Merge "Fix license texts." am: 76908de5ca am: 17c50b7b1c am: f21ab58e57 am: efdcda607c

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

Change-Id: Ibe948888c8637ddb85d5596b88305e5b85a5a9ec
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index cfc6773..7f01127 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,6 +1,7 @@
 [Builtin Hooks]
 clang_format = true
 xmllint = true
+bpfmt = true
 
 [Builtin Hooks Options]
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
@@ -10,46 +11,6 @@
 
 [Hook Scripts]
 checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
-                  -fw apps/CtsVerifier/
-                      apps/CtsVerifierUSBCompanion/
-                      common/device-side/bedstead/
-                      common/device-side/util/
-                      hostsidetests/car/
-                      hostsidetests/devicepolicy
-                      hostsidetests/dumpsys
-                      hostsidetests/graphics
-                      hostsidetests/inputmethodservice/
-                      hostsidetests/multiuser/
-                      hostsidetests/scopedstorage/
-                      hostsidetests/stagedinstall/
-                      hostsidetests/userspacereboot/
-                      libs/
-                      tests/app/
-                      tests/autofillservice/
-                      tests/contentcaptureservice/
-                      tests/devicepolicy/
-                      tests/inputmethod/
-                      tests/tests/animation/
-                      tests/tests/carrierapi/
-                      tests/tests/content/
-                      tests/tests/graphics/
-                      tests/tests/hardware/
-                      tests/tests/packageinstaller/atomicinstall/
-                      tests/tests/permission2/
-                      tests/tests/permission/
-                      tests/tests/preference/
-                      tests/tests/print/
-                      tests/tests/telephony/
-                      tests/tests/telephony2/
-                      tests/tests/telephony3/
-                      tests/tests/telephony4/
-                      tests/tests/telephonyprovider/
-                      tests/tests/text/
-                      tests/tests/theme/
-                      tests/tests/transition/
-                      tests/tests/uirendering/
-                      tests/tests/view/
-                      tests/tests/widget/
 
 ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py -f ${PREUPLOAD_FILES}
 
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index c52e57e..02ef57c 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -61,7 +61,7 @@
 
 
 
-for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils scene_change_utils target_exposure_utils
+for M in sensor_fusion_utils camera_properties_utils capture_request_utils opencv_processing_utils image_processing_utils its_session_utils target_exposure_utils
 do
     python "utils/$M.py" 2>&1 | grep -q "OK" || \
         echo ">> Unit test for $M failed" >&2
diff --git a/apps/CameraITS/config.yml b/apps/CameraITS/config.yml
index fc712b3..2a668c0 100644
--- a/apps/CameraITS/config.yml
+++ b/apps/CameraITS/config.yml
@@ -28,7 +28,8 @@
       brightness: 96
       chart_distance: 31.0
       debug_mode: "False"  # quotes are needed here
-      chart_loc_arg: ""
+      lighting_cntl: <controller-type>  # can be arduino or "None"
+      lighting_ch: <controller-channel>
       camera: <camera-id>
       scene: <scene-name>  # if <scene-name> left as-is runs all scenes
 
diff --git a/apps/CameraITS/tests/its_base_test.py b/apps/CameraITS/tests/its_base_test.py
index afa0128..87ba7a8 100644
--- a/apps/CameraITS/tests/its_base_test.py
+++ b/apps/CameraITS/tests/its_base_test.py
@@ -22,6 +22,7 @@
 from mobly.controllers import android_device
 
 import its_session_utils
+import lighting_control_utils
 
 ADAPTIVE_BRIGHTNESS_OFF = '0'
 TABLET_CMD_DELAY_SEC = 0.5  # found empirically
@@ -37,8 +38,7 @@
 NOT_YET_MANDATED = {
     'scene0': [['test_test_patterns', 30],
                ['test_tonemap_curve', 30]],
-    'scene1_1': [['test_ae_precapture_trigger', 28],
-                 ['test_channel_saturation', 29]],
+    'scene1_1': [['test_ae_precapture_trigger', 28]],
     'scene1_2': [],
     'scene2_a': [['test_jpeg_quality', 30]],
     'scene2_b': [['test_auto_per_frame_control', NOT_YET_MANDATED_ALL]],
@@ -50,7 +50,6 @@
     'scene5': [],
     'scene6': [['test_zoom', 30]],
     'sensor_fusion': [],
-    'scene_change': [['test_scene_change', 31]]
 }
 
 
@@ -77,10 +76,13 @@
     if self.user_params.get('chart_distance'):
       self.chart_distance = float(self.user_params['chart_distance'])
       logging.debug('Chart distance: %s cm', self.chart_distance)
-    if self.user_params.get('chart_loc_arg'):
-      self.chart_loc_arg = self.user_params['chart_loc_arg']
+    if (self.user_params.get('lighting_cntl') and
+        self.user_params.get('lighting_ch')):
+      self.lighting_cntl = self.user_params['lighting_cntl']
+      self.lighting_ch = str(self.user_params['lighting_ch'])
     else:
-      self.chart_loc_arg = ''
+      self.lighting_cntl = 'None'
+      self.lighting_ch = '1'
     if self.user_params.get('debug_mode'):
       self.debug_mode = True if self.user_params[
           'debug_mode'] == 'True' else False
@@ -115,6 +117,15 @@
 
     self._setup_devices(num_devices)
 
+    arduino_serial_port = lighting_control_utils.lighting_control(
+      self.lighting_cntl, self.lighting_ch)
+    if arduino_serial_port:
+      lighting_control_utils.set_light_brightness(
+      self.lighting_ch, 255, arduino_serial_port)
+      logging.debug('Light is turned ON.')
+    else:
+      logging.info('Ensure lights ON')
+
   def _setup_devices(self, num):
     """Sets up each device in parallel if more than one device."""
     if num not in VALID_NUM_DEVICES:
@@ -132,6 +143,7 @@
   def setup_dut(self, device):
     self.dut.adb.shell(
         'am start -n com.android.cts.verifier/.CtsVerifierActivity')
+    logging.debug('Setting up device: %s', str(device))
     # Wait for the app screen to appear.
     time.sleep(WAIT_TIME_SEC)
 
@@ -171,17 +183,18 @@
     logging.debug('dumpsys window output: %s', output.decode('utf-8').strip())
     output_list = str(output.decode('utf-8')).strip().split(' ')
     for val in output_list:
-        if 'LandscapeRotation' in val:
-            landscape_val = str(val.split('=')[-1])
-            # For some tablets the values are in constant forms such as ROTATION_90
-            if 'ROTATION_90' in landscape_val:
-                landscape_val = '1'
-            logging.debug('Changing the orientation to landscape mode.')
-            self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation',
-                                   landscape_val])
-            break
-    logging.debug('Reported tablet orientation is: %d',
-                  int(self.tablet.adb.shell('settings get system user_rotation')))
+      if 'LandscapeRotation' in val:
+        landscape_val = str(val.split('=')[-1])
+        # For some tablets the values are in constant forms such as ROTATION_90
+        if 'ROTATION_90' in landscape_val:
+          landscape_val = '1'
+        logging.debug('Changing the orientation to landscape mode.')
+        self.tablet.adb.shell(['settings', 'put', 'system', 'user_rotation',
+                               landscape_val])
+        break
+    logging.debug(
+        'Reported tablet orientation is: %d',
+        int(self.tablet.adb.shell('settings get system user_rotation')))
 
   def parse_hidden_camera_id(self):
     """Parse the string of camera ID into an array.
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index b0995a7..c6a69a3 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -23,6 +23,8 @@
 import capture_request_utils
 import its_session_utils
 
+_HYPERFOCAL_MIN = 0.02
+
 
 class MetadataTest(its_base_test.ItsBaseTest):
   """Test the validity of some metadata entries.
@@ -119,7 +121,8 @@
         check(self, props['android.sensor.blackLevelPattern'] is not None,
               'props["android.sensor.blackLevelPattern"] is not None')
 
-      assert not self.failed
+      if self.failed:
+        raise AssertionError('props failure. Check test_log.DEBUG.')
 
       if not camera_properties_utils.legacy(props):
         # Test: pixel_pitch, FOV, and hyperfocal distance are reasonable
@@ -132,23 +135,27 @@
         pixel_pitch_w = (sensor_size['width'] / fmts[0]['width'] * 1E3)
         logging.debug('Assert pixel_pitch WxH: %.2f um, %.2f um', pixel_pitch_w,
                       pixel_pitch_h)
-        assert 0.7 <= pixel_pitch_w <= 10
-        assert 0.7 <= pixel_pitch_h <= 10
-        assert 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0
+        if (not 0.7 <= pixel_pitch_w <= 10 or
+            not 0.7 <= pixel_pitch_h <= 10 or
+            not 0.333 <= pixel_pitch_w/pixel_pitch_h <= 3.0):
+          raise AssertionError(
+              f'Pixel pitch error! w: {pixel_pitch_w}, h: {pixel_pitch_h}')
 
         diag = math.sqrt(sensor_size['height']**2 + sensor_size['width']**2)
         fl = md['android.lens.focalLength']
         logging.debug('Focal length: %.3f', fl)
         fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
         logging.debug('Assert field of view: %.1f degrees', fov)
-        assert 10 <= fov <= 130
+        if not 10 <= fov <= 130:
+          raise AssertionError(f'FoV error: {fov:.1f}')
 
         if camera_properties_utils.lens_approx_calibrated(props):
           diopter_hyperfocal = props['android.lens.info.hyperfocalDistance']
           if diopter_hyperfocal != 0.0:
             hyperfocal = 1.0 / diopter_hyperfocal
-            logging.debug('Assert hyperfocal distance: %.2f m', hyperfocal)
-            assert 0.02 <= hyperfocal
+            if _HYPERFOCAL_MIN > hyperfocal:
+              raise AssertionError('hyperfocal distance error: '
+                                   f'{hyperfocal:.2f}, MIN: {_HYPERFOCAL_MIN}')
 
         logging.debug('Minimum focus distance: %3.f',
                       props['android.lens.info.minimumFocusDistance'])
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
index 863ffbc..b5f672d 100644
--- a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -46,7 +46,8 @@
       sens_step = (sens_range[1] - sens_range[0]) // NUM_STEPS
       sens_list = range(sens_range[0], sens_range[1], sens_step)
       exp = min(props['android.sensor.info.exposureTimeRange'])
-      assert exp != 0
+      if exp == 0:
+        raise AssertionError('Minimum exposure time is 0')
       reqs = [
           capture_request_utils.manual_capture_request(s, exp)
           for s in sens_list
@@ -56,11 +57,10 @@
       caps = cam.do_capture(reqs, fmt)
       for i, cap in enumerate(caps):
         s_req = sens_list[i]
-        s_res = cap['metadata']['android.sensor.sensitivity']
-        msg = 's_write: %d, s_read: %d, TOL: %.2f' % (s_req, s_res,
-                                                      ERROR_TOLERANCE)
-        assert s_req >= s_res, msg
-        assert s_res / float(s_req) > ERROR_TOLERANCE, msg
+        s_cap = cap['metadata']['android.sensor.sensitivity']
+        if (s_req < s_cap or s_cap / float(s_req) < ERROR_TOLERANCE):
+          raise AssertionError(f's_request: {s_req}, s_capture: {s_cap}, '
+                               f'TOL: {ERROR_TOLERANCE:.2f}')
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_read_write.py b/apps/CameraITS/tests/scene0/test_read_write.py
index e8b514a..32bcd56 100644
--- a/apps/CameraITS/tests/scene0/test_read_write.py
+++ b/apps/CameraITS/tests/scene0/test_read_write.py
@@ -56,7 +56,8 @@
       logging.debug('sensor sensitivity range: %s', sens_range)
 
       # determine if exposure test range is within sensor reported range
-      assert sensor_exp_range[0] != 0
+      if sensor_exp_range[0] == 0:
+        raise AssertionError('Min expsoure == 0')
       exp_range = []
       if sensor_exp_range[0] < TEST_EXP_RANGE[0]:
         exp_range.append(TEST_EXP_RANGE[0])
@@ -135,8 +136,11 @@
             logging.debug('e_write: %d, e_read: %d, RTOL: %.2f',
                           fail['e_write'], fail['e_read'], RTOL_EXP_GAIN)
 
-        # assert PASS/FAIL
-        assert not e_failed + s_failed
+        # PASS/FAIL
+        if e_failed:
+          raise AssertionError(f'Exposure fails: {e_failed}')
+        if s_failed:
+          raise AssertionError(f'Sensitivity fails: {s_failed}')
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
index e8afbb2..f5c165f 100644
--- a/apps/CameraITS/tests/scene0/test_sensor_events.py
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -51,9 +51,9 @@
       for key, existing in sensors.items():
         # Vibrator does not return any sensor event. b/142653973
         if existing and key != 'vibrator':
-          e_msg = 'Sensor %s has no events!' % key
           # Check len(events[key]) > 0
-          assert events[key], e_msg
+          if not events[key]:
+            raise AssertionError(f'Sensor {key} has no events!')
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_test_patterns.py b/apps/CameraITS/tests/scene0/test_test_patterns.py
index dbf1ee2..6aced4e 100644
--- a/apps/CameraITS/tests/scene0/test_test_patterns.py
+++ b/apps/CameraITS/tests/scene0/test_test_patterns.py
@@ -153,10 +153,11 @@
                                          True)
 
       # Check pattern for correctness
-      assert check_pattern(cap, props, pattern)
+      if not check_pattern(cap, props, pattern):
+        raise AssertionError(f'Pattern {pattern} failed')
     else:
       logging.debug('%d not in android.sensor.availableTestPatternModes.',
-                    (pattern))
+                    pattern)
 
 
 class TestPatterns(its_base_test.ItsBaseTest):
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
index f7e5991..a4c83ad 100644
--- a/apps/CameraITS/tests/scene0/test_unified_timestamps.py
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -63,7 +63,8 @@
       for sensor, existing in sensors.items():
       # Vibrator doesn't generate outputs: b/142653973
         if existing and sensor != 'vibrator':
-          assert events[sensor], '%s sensor has no events!' % sensor
+          if not events[sensor]:
+            raise AssertionError(f'{sensor} has no events!')
           ts_sensor_first[sensor] = events[sensor][0]['time']
           ts_sensor_last[sensor] = events[sensor][-1]['time']
 
@@ -78,8 +79,12 @@
         if existing and sensor != 'vibrator':
           logging.debug('%s timestamps: %d %d', sensor, ts_sensor_first[sensor],
                         ts_sensor_last[sensor])
-          assert ts_image0 < ts_sensor_first[sensor] < ts_image1
-          assert ts_image0 < ts_sensor_last[sensor] < ts_image1
+          if (not ts_image0 < ts_sensor_first[sensor] < ts_image1 or
+              not ts_image0 < ts_sensor_last[sensor] < ts_image1):
+            raise AssertionError(
+                f'{sensor} times not bounded by camera! camera: '
+                f'{ts_image0}:{ts_image1}, {sensor}: '
+                f'{ts_sensor_first[sensor]}:{ts_sensor_last[sensor]}')
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene0/test_vibration_restriction.py b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
index 45c82e9..0ece4b0 100644
--- a/apps/CameraITS/tests/scene0/test_vibration_restriction.py
+++ b/apps/CameraITS/tests/scene0/test_vibration_restriction.py
@@ -102,10 +102,10 @@
           'Accel variance with/without/restricted vibration (%f, %f, %f)',
           var_w_vibration, var_wo_vibration, var_w_vibration_restricted)
 
-      e_msg = 'Device vibrated while vibration is muted'
       vibration_variance = var_w_vibration_restricted < (
           var_wo_vibration * THRESHOLD_VIBRATION_VAR)
-      assert vibration_variance, e_msg
+      if not vibration_variance:
+        raise AssertionError('Device vibrated while vibration is muted.')
 
 
 if __name__ == '__main__':
diff --git a/apps/CameraITS/tests/scene1_1/test_3a.py b/apps/CameraITS/tests/scene1_1/test_3a.py
index 9db3bdc..9100e79 100644
--- a/apps/CameraITS/tests/scene1_1/test_3a.py
+++ b/apps/CameraITS/tests/scene1_1/test_3a.py
@@ -28,6 +28,11 @@
 NAME = os.path.splitext(os.path.basename(__file__))[0]
 
 
+def assert_is_number(x):
+  if np.isnan(x):
+    raise AssertionError(f'{x} is not a number!')
+
+
 class ThreeATest(its_base_test.ItsBaseTest):
   """Test basic camera 3A behavior.
 
@@ -57,15 +62,22 @@
       logging.debug('AE: sensitivity %d, exposure %dns', s, e)
       logging.debug('AF: distance %.3f', focus)
 
-      assert len(awb_gains) == AWB_GAINS_LENGTH
+      if len(awb_gains) != AWB_GAINS_LENGTH:
+        raise AssertionError(
+            f'AWB gains has unexpected # of terms! {awb_gains}')
       for g in awb_gains:
-        assert not np.isnan(g)
-      assert len(awb_xform) == AWB_XFORM_LENGTH
+        assert_is_number(g)
+      if len(awb_xform) != AWB_XFORM_LENGTH:
+        raise AssertionError(
+            f'AWB transform has unexpected # of terms! {awb_xform}')
       for x in awb_xform:
-        assert not np.isnan(x)
-      assert s > 0
-      assert e > 0
-      assert focus >= 0
+        assert_is_number(x)
+      if s <= 0:
+        raise AssertionError(f'sensitivity {s} <= 0!')
+      if e <= 0:
+        raise AssertionError(f'exposure {e} <= 0!')
+      if focus < 0:
+        raise AssertionError(f'focus distance {focus} < 0!')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
index dae3c05..49886a5 100644
--- a/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
+++ b/apps/CameraITS/tests/scene1_1/test_ae_precapture_trigger.py
@@ -86,8 +86,8 @@
         state = cap['metadata']['android.control.aeState']
         msg = 'AE state after manual request %d: %d' % (i, state)
         logging.debug('%s', msg)
-        e_msg = msg + ' AE_INACTIVE: %d' % AE_INACTIVE
-        assert state == AE_INACTIVE, e_msg
+        if state != AE_INACTIVE:
+          raise AssertionError(f'{msg} AE_INACTIVE: {AE_INACTIVE}')
 
       # Capture auto request and verify the AE state: no trigger.
       logging.debug('Auto capture')
@@ -97,9 +97,9 @@
       state = cap['metadata']['android.control.aeState']
       msg = 'AE state after auto request: %d' % state
       logging.debug('%s', msg)
-      e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d' % (
-          AE_SEARCHING, AE_CONVERGED)
-      assert state in [AE_SEARCHING, AE_CONVERGED], e_msg
+      if state not in [AE_SEARCHING, AE_CONVERGED]:
+        raise AssertionError(f'{msg} AE_SEARCHING: {AE_SEARCHING}, '
+                             f'AE_CONVERGED: {AE_CONVERGED}')
 
       # Capture auto request with a precapture trigger.
       logging.debug('Auto capture with precapture trigger')
@@ -108,9 +108,10 @@
       state = cap['metadata']['android.control.aeState']
       msg = 'AE state after auto request with precapture trigger: %d' % state
       logging.debug('%s', msg)
-      e_msg = msg + ' AE_SEARCHING: %d, AE_CONVERGED: %d, AE_PRECAPTURE: %d' % (
-          AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE)
-      assert state in [AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE], e_msg
+      if state not in [AE_SEARCHING, AE_CONVERGED, AE_PRECAPTURE]:
+        raise AssertionError(f'{msg} AE_SEARCHING: {AE_SEARCHING}, '
+                             f'AE_CONVERGED: {AE_CONVERGED}, '
+                             f'AE_PRECAPTURE: {AE_PRECAPTURE}')
 
       # Capture some more auto requests, and AE should converge.
       logging.debug('Additional auto captures')
@@ -122,8 +123,8 @@
         logging.debug('%s', msg)
         if state == AE_CONVERGED:
           return
-      e_msg = msg + ' AE_CONVERGED: %d' % AE_CONVERGED
-      assert state == AE_CONVERGED, e_msg
+      if state != AE_CONVERGED:
+        raise AssertionError(f'{msg}  AE_CONVERGED: {AE_CONVERGED}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
index 72c791d..2cf6247 100644
--- a/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_auto_vs_manual.py
@@ -123,22 +123,22 @@
 
       # Check AWB gains & transform in manual results match values from do_3a
       for g, x in [(awb_gains_m1, awb_xform_m1), (awb_gains_m2, awb_xform_m2)]:
-        e_msg = 'awb_xform 3A: %s, manual: %s, ATOL=%.2f' % (
-            str(awb_xform), str(x), AWB_MANUAL_ATOL)
-        assert np.allclose(awb_xform, x, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
-        e_msg = 'awb_gains 3A: %s, manual: %s, ATOL=%.2f' % (
-            str(awb_gains), str(g), AWB_MANUAL_ATOL)
-        assert np.allclose(awb_gains, g, atol=AWB_MANUAL_ATOL, rtol=0), e_msg
+        if not np.allclose(awb_xform, x, atol=AWB_MANUAL_ATOL, rtol=0):
+          raise AssertionError(
+              f'awb_xform 3A: {awb_xform}, manual: {x}, ATOL={AWB_MANUAL_ATOL}')
+        if not np.allclose(awb_gains, g, atol=AWB_MANUAL_ATOL, rtol=0):
+          raise AssertionError(
+              f'awb_gains 3A: {awb_gains}, manual: {g}, ATOL={AWB_MANUAL_ATOL}')
 
       # Check AWB gains & transform in auto results match values from do_3a
-      e_msg = 'awb_xform 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
-          str(awb_xform), str(awb_xform_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
-      assert np.allclose(awb_xform_a, awb_xform, atol=AWB_AUTO_ATOL,
-                         rtol=AWB_AUTO_RTOL), e_msg
-      e_msg = 'awb_gains 3A: %s, auto: %s, RTOL=%.2f, ATOL=%.2f' % (
-          str(awb_gains), str(awb_gains_a), AWB_AUTO_RTOL, AWB_AUTO_ATOL)
-      assert np.allclose(awb_gains_a, awb_gains, atol=AWB_AUTO_ATOL,
-                         rtol=AWB_AUTO_RTOL), e_msg
+      if not np.allclose(awb_xform_a, awb_xform, atol=AWB_AUTO_ATOL,
+                         rtol=AWB_AUTO_RTOL):
+        raise AssertionError(f'awb_xform 3A: {awb_xform}, auto: {awb_xform_a},'
+                             f'RTOL={AWB_AUTO_RTOL}, ATOL={AWB_AUTO_ATOL}')
+      if not np.allclose(awb_gains_a, awb_gains, atol=AWB_AUTO_ATOL,
+                         rtol=AWB_AUTO_RTOL):
+        raise AssertionError(f'awb_gains 3A: {awb_gains}, auto: {awb_gains_a},'
+                             f'RTOL={AWB_AUTO_RTOL}, ATOL={AWB_AUTO_ATOL}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_black_white.py b/apps/CameraITS/tests/scene1_1/test_black_white.py
index 2421cc9..88ff723 100644
--- a/apps/CameraITS/tests/scene1_1/test_black_white.py
+++ b/apps/CameraITS/tests/scene1_1/test_black_white.py
@@ -15,6 +15,7 @@
 
 
 import logging
+import math
 import os.path
 import matplotlib
 from matplotlib import pylab
@@ -29,6 +30,7 @@
 import image_processing_utils
 import its_session_utils
 
+_ANDROID10_API_LEVEL = 29
 CH_FULL_SCALE = 255
 CH_THRESH_BLACK = 6
 CH_THRESH_WHITE = CH_FULL_SCALE - 6
@@ -137,21 +139,23 @@
 
       # Assert blacks below CH_THRESH_BLACK
       for ch, mean in enumerate(black_means):
-        e_msg = '%s black: %.1f, THRESH: %.f' % (
-            COLOR_PLANES[ch], mean, CH_THRESH_BLACK)
-        assert mean < CH_THRESH_BLACK, e_msg
+        if mean >= CH_THRESH_BLACK:
+          raise AssertionError(f'{COLOR_PLANES[ch]} black: {mean:.1f}, '
+                               f'THRESH: {CH_THRESH_BLACK}')
 
       # Assert whites above CH_THRESH_WHITE
       for ch, mean in enumerate(white_means):
-        e_msg = '%s white: %.1f, THRESH: %.f' % (
-            COLOR_PLANES[ch], mean, CH_THRESH_WHITE)
-        assert mean > CH_THRESH_WHITE, e_msg
+        if mean <= CH_THRESH_WHITE:
+          raise AssertionError(f'{COLOR_PLANES[ch]} white: {mean:.1f}, '
+                               f'THRESH: {CH_THRESH_WHITE}')
 
       # Assert channels saturate evenly (was test_channel_saturation)
-      e_msg = 'ch saturation not equal! RGB: %s, ATOL: %.f' % (
-          str(white_means), CH_TOL_WHITE)
-      assert np.isclose(
-          np.amin(white_means), np.amax(white_means), atol=CH_TOL_WHITE), e_msg
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      if first_api_level >= _ANDROID10_API_LEVEL:
+        if not math.isclose(
+            np.amin(white_means), np.amax(white_means), abs_tol=CH_TOL_WHITE):
+          raise AssertionError('channel saturation not equal! '
+                               f'RGB: {white_means}, ATOL: {CH_TOL_WHITE}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
index b43067c..8354cf1 100644
--- a/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
+++ b/apps/CameraITS/tests/scene1_1/test_burst_sameness_manual.py
@@ -128,11 +128,10 @@
       # PASS/FAIL based on center patch similarity.
       for plane, means in enumerate([r_means, g_means, b_means]):
         spread = max(means) - min(means)
-        msg = '%s spread: %.5f, spread_thresh: %.2f' % (
-            COLORS[plane], spread, spread_thresh)
-        logging.debug('%s', msg)
-        assert spread < spread_thresh, msg
+        logging.debug('%s spread: %.5f', COLORS[plane], spread)
+        if spread > spread_thresh:
+          raise AssertionError(f'{COLORS[plane]} spread > THRESH. spread: '
+                               f'{spread}, THRESH: {spread_thresh:.2f}')
 
 if __name__ == '__main__':
   test_runner.main()
-
diff --git a/apps/CameraITS/tests/scene1_1/test_capture_result.py b/apps/CameraITS/tests/scene1_1/test_capture_result.py
index 73dfe5a..304a737 100644
--- a/apps/CameraITS/tests/scene1_1/test_capture_result.py
+++ b/apps/CameraITS/tests/scene1_1/test_capture_result.py
@@ -83,8 +83,10 @@
     logging.debug('AWB region: %s', str(metadata['android.control.awbRegions']))
 
   # Color correction gains and transform should be the same size
-  assert len(awb_gains) == AWB_GAINS_NUM
-  assert len(awb_xform) == AWB_XFORM_NUM
+  if len(awb_gains) != AWB_GAINS_NUM:
+    raise AssertionError(f'AWB gains wrong length! {awb_gains}')
+  if len(awb_xform) != AWB_XFORM_NUM:
+    raise AssertionError(f'AWB transform wrong length! {awb_xform}')
 
 
 def test_auto(cam, props, log_path):
@@ -110,23 +112,33 @@
 
   ctrl_mode = metadata['android.control.mode']
   logging.debug('Control mode: %d', ctrl_mode)
-  assert ctrl_mode == 1, 'ctrl_mode: %d' % ctrl_mode
+  if ctrl_mode != 1:
+    raise AssertionError(f'ctrl_mode != 1: {ctrl_mode}')
 
   # Color correction gain and transform must be valid.
   metadata_checks(metadata, props)
   awb_gains = metadata['android.colorCorrection.gains']
   awb_xform = metadata['android.colorCorrection.transform']
-  assert all([g > 0 for g in awb_gains])
-  assert all([t['denominator'] != 0 for t in awb_xform])
+  if not all([g > 0 for g in awb_gains]):
+    raise AssertionError(f'AWB gains has negative terms: {awb_gains}')
+  if not all([t['denominator'] != 0 for t in awb_xform]):
+    raise AssertionError(f'AWB transform has 0 denominators: {awb_xform}')
 
   # Color correction should not match the manual settings.
-  assert not np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL)
-  assert not all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
-                  for i in range(AWB_XFORM_NUM)])
+  if np.allclose(awb_gains, MANUAL_AWB_GAINS, atol=ISCLOSE_ATOL):
+    raise AssertionError('Manual and automatic AWB gains are same! '
+                         f'manual: {MANUAL_AWB_GAINS}, auto: {awb_gains}, '
+                         f'ATOL: {ISCLOSE_ATOL}')
+  if all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+          for i in range(AWB_XFORM_NUM)]):
+    raise AssertionError('Manual and automatic AWB transforms are same! '
+                         f'manual: {MANUAL_AWB_XFORM}, auto: {awb_xform}, '
+                         f'ATOL: {ISCLOSE_ATOL}')
 
   # Exposure time must be valid.
   exp_time = metadata['android.sensor.exposureTime']
-  assert exp_time > 0
+  if exp_time <= 0:
+    raise AssertionError(f'exposure time is <= 0! {exp_time}')
 
   # Draw lens shading correction map
   lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
@@ -173,35 +185,47 @@
 
   ctrl_mode = metadata['android.control.mode']
   logging.debug('Control mode: %d', ctrl_mode)
-  assert ctrl_mode == 0, 'ctrl_mode: %d' % ctrl_mode
+  if ctrl_mode != 0:
+    raise AssertionError(f'ctrl_mode: {ctrl_mode}')
 
   # Color correction gains and transform should be the same size and
   # values as the manually set values.
   metadata_checks(metadata, props)
   awb_gains = metadata['android.colorCorrection.gains']
   awb_xform = metadata['android.colorCorrection.transform']
-  assert (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i],
+  if not (all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[0][i],
                           atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
           all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[1][i],
                           atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]) or
           all([np.isclose(awb_gains[i], MANUAL_GAINS_OK[2][i],
-                          atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)]))
-  assert (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
-               for i in range(AWB_XFORM_NUM)]))
+                          atol=ISCLOSE_ATOL) for i in range(AWB_GAINS_NUM)])):
+    raise AssertionError('request/capture mismatch in AWB gains! '
+                         f'req: {MANUAL_GAINS_OK}, cap: {awb_gains}, '
+                         f'ATOL: {ISCLOSE_ATOL}')
+  if not (all([is_close_rational(awb_xform[i], MANUAL_AWB_XFORM[i])
+               for i in range(AWB_XFORM_NUM)])):
+    raise AssertionError('request/capture mismatch in AWB transforms! '
+                         f'req: {MANUAL_AWB_XFORM}, cap: {awb_xform}, '
+                         f'ATOL: {ISCLOSE_ATOL}')
 
   # The returned tonemap must be linear.
   curves = [metadata['android.tonemap.curve']['red'],
             metadata['android.tonemap.curve']['green'],
             metadata['android.tonemap.curve']['blue']]
   logging.debug('Tonemap: %s', str(curves[0][1::16]))
-  for c in curves:
-    assert c, 'c in curves is empty.'
-    assert all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL)
-                for i in range(0, len(c), 2)])
+  for j, c in enumerate(curves):
+    if not c:
+      raise AssertionError('c in curves is empty.')
+    if not all([np.isclose(c[i], c[i+1], atol=ISCLOSE_ATOL)
+                for i in range(0, len(c), 2)]):
+      raise AssertionError(f"tonemap 'RGB'[i] is not linear! {c}")
 
   # Exposure time must be close to the requested exposure time.
   exp_time = metadata['android.sensor.exposureTime']
-  assert np.isclose(exp_time*1.0E-6, exp_min*1.0E-6, atol=ISCLOSE_ATOL)
+  if not np.isclose(exp_time, exp_min, atol=ISCLOSE_ATOL/1E-06):
+    raise AssertionError('request/capture exposure time mismatch! '
+                         f'req: {exp_min}, cap: {exp_time}, '
+                         f'ATOL: {ISCLOSE_ATOL/1E-6}')
 
   # Lens shading map must be valid
   lsc_obj = metadata['android.statistics.lensShadingCorrectionMap']
@@ -209,9 +233,11 @@
   lsc_map_w = lsc_obj['width']
   lsc_map_h = lsc_obj['height']
   logging.debug('LSC map: %dx%d, %s', lsc_map_w, lsc_map_h, str(lsc_map[:8]))
-  assert (lsc_map_w > 0 and lsc_map_h > 0 and
-          lsc_map_w*lsc_map_h*4 == len(lsc_map))
-  assert all([m >= 1 for m in lsc_map])
+  if not (lsc_map_w > 0 and lsc_map_h > 0 and
+          lsc_map_w*lsc_map_h*4 == len(lsc_map)):
+    raise AssertionError(f'Incorrect lens shading map size! {lsc_map}')
+  if not all([m >= 1 for m in lsc_map]):
+    raise AssertionError(f'Lens shading map has negative vals! {lsc_map}')
 
   # Draw lens shading correction map
   draw_lsc_plot(lsc_map_w, lsc_map_h, lsc_map, 'manual', log_path)
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
index 69cc33f..8a598bd 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_region_raw.py
@@ -71,7 +71,8 @@
 
       # Calculate a center crop region.
       zoom = min(3.0, camera_properties_utils.get_max_digital_zoom(props))
-      assert zoom >= 1, 'zoom: %.2f' % zoom
+      if zoom < 1:
+        raise AssertionError(f'zoom: {zoom:.2f}')
       crop_w = aw // zoom
       crop_h = ah // zoom
 
@@ -129,13 +130,13 @@
           ex = aw * err_delta
           ey = ah * err_delta
         logging.debug('error X, Y: %.2f, %.2f', ex, ey)
-        e_msg = 'expected: %s, reported: %s, ex: %.2f, ex: %.2f' % (
-            str(cr_expected), str(cr_reported), ex, ey)
-        assert (
+        if not (
             (abs(cr_expected['left'] - cr_reported['left']) <= ex) and
             (abs(cr_expected['right'] - cr_reported['right']) <= ex) and
             (abs(cr_expected['top'] - cr_reported['top']) <= ey) and
-            (abs(cr_expected['bottom'] - cr_reported['bottom']) <= ey)), e_msg
+            (abs(cr_expected['bottom'] - cr_reported['bottom']) <= ey)):
+          raise AssertionError(f'expected: {cr_expected}, reported: '
+                               f'{cr_reported}, ex: {ex:.2f}, ey: {ey:.2f}')
 
       # Also check the image content; 3 of the 4 shots should match.
       # Note that all the shots are RGB below; the variable names correspond
@@ -178,10 +179,12 @@
       logging.debug('YUV diff (crop vs. non-crop): %.3f', diff_yuv)
       logging.debug('RAW diff (crop vs. non-crop): %.3f', diff_raw)
 
-      assert diff_yuv > DIFF_THRESH, 'diff_yuv: %.3f, THRESH: %.2f' % (
-          diff_yuv, DIFF_THRESH)
-      assert diff_raw < DIFF_THRESH, 'diff_raw: %.3f, THRESH: %.2f' % (
-          diff_raw, DIFF_THRESH)
+      if diff_yuv <= DIFF_THRESH:
+        raise AssertionError('YUV diff too small! '
+                             f'diff_yuv: {diff_yuv:.3f}, THRESH: {DIFF_THRESH}')
+      if diff_raw >= DIFF_THRESH:
+        raise AssertionError('RAW diff too big! '
+                             f'diff_raw: {diff_raw:.3f}, THRESH: {DIFF_THRESH}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_crop_regions.py b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
index 77e35ed..8e979a3 100644
--- a/apps/CameraITS/tests/scene1_1/test_crop_regions.py
+++ b/apps/CameraITS/tests/scene1_1/test_crop_regions.py
@@ -68,9 +68,9 @@
 
       # Uses a 2x digital zoom.
       max_digital_zoom = capture_request_utils.get_max_digital_zoom(props)
-      e_msg = 'Max digital zoom: %d, THRESH: %d' % (max_digital_zoom,
-                                                    MIN_DIGITAL_ZOOM_THRESH)
-      assert max_digital_zoom >= MIN_DIGITAL_ZOOM_THRESH, e_msg
+      if max_digital_zoom < MIN_DIGITAL_ZOOM_THRESH:
+        raise AssertionError(f'Max digital zoom: {max_digital_zoom}, '
+                             f'THRESH: {MIN_DIGITAL_ZOOM_THRESH}')
 
       # Capture a full frame.
       req = capture_request_utils.manual_capture_request(s, e)
@@ -95,6 +95,7 @@
         reqs.append(req)
       caps_regions = cam.do_capture(reqs)
       match_failed = False
+      e_msg = []
       for i, cap in enumerate(caps_regions):
         a = cap['metadata']['android.scaler.cropRegion']
         ax, ay = a['left'], a['top']
@@ -125,11 +126,13 @@
             min_diff_region = j
         if i != min_diff_region:
           match_failed = True
+          e_msg.append(f'i != min_diff_region. i: {i}, '
+                       f'min_diff_region: {min_diff_region}. ')
         logging.debug('Crop image %d (%d,%d %dx%d) best match with region %d',
                       i, ax, ay, aw, ah, min_diff_region)
 
-    assert not match_failed
+    if match_failed:
+      raise AssertionError(f'Match failed: {e_msg}')
 
 if __name__ == '__main__':
   test_runner.main()
-
diff --git a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
index 8af5aed..3589586 100644
--- a/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
+++ b/apps/CameraITS/tests/scene1_1/test_dng_noise_model.py
@@ -103,7 +103,9 @@
 
         # Test each raw color channel (R, GR, GB, B)
         noise_profile = cap['metadata']['android.sensor.noiseProfile']
-        assert len(noise_profile) == len(BAYER_LIST)
+        if len(noise_profile) != len(BAYER_LIST):
+          raise AssertionError(
+              f'noise_profile wrong length! {len(noise_profile)}')
         for i, ch in enumerate(BAYER_LIST):
           # Get the noise model parameters for this channel of this shot.
           s, o = noise_profile[cfa_idxs[i]]
@@ -129,11 +131,11 @@
           # so the check remains correct even after the signal starts to clip.
           mean_minus_3sigma = mean_img_ch - math.sqrt(var_model) * 3
           if mean_minus_3sigma < 0:
-            e_msg = 'Pixel distribution crosses 0. Likely black level '
-            e_msg += 'over-clips. Linear model is not valid. '
-            e_msg += 'mean: %.3e, var: %.3e, u-3s: %.3e' % (
-                mean_img_ch, var_model, mean_minus_3sigma)
-            assert mean_minus_3sigma < 0, e_msg
+            if mean_minus_3sigma >= 0:
+              raise AssertionError(
+                  'Pixel distribution crosses 0. Likely black level over-clips.'
+                  f' Linear model is not valid. mean: {mean_img_ch:.3e},'
+                  f' var: {var_model:.3e}, u-3s: {mean_minus_3sigma:.3e}')
           else:
             var = image_processing_utils.compute_image_variances(patch_norm)[0]
             var_meas[i].append(var)
@@ -167,7 +169,8 @@
       logging.debug('%s variance diffs: %s', ch, str(var_diffs))
       for j, diff in enumerate(var_diffs):
         thresh = max(VAR_ATOL_THRESH, VAR_RTOL_THRESH*var_exp[i][j])
-        assert diff <= thresh, 'var diff: %.5f, thresh: %.4f' % (diff, thresh)
+        if diff > thresh:
+          raise AssertionError(f'var diff: {diff:.5f}, thresh: {thresh:.4f}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_jpeg.py b/apps/CameraITS/tests/scene1_1/test_jpeg.py
index 2c11d29..cc16a05 100644
--- a/apps/CameraITS/tests/scene1_1/test_jpeg.py
+++ b/apps/CameraITS/tests/scene1_1/test_jpeg.py
@@ -100,9 +100,9 @@
       rms_diff = image_processing_utils.compute_image_rms_difference(
           rgb_means_yuv, rgb_means_jpg)
       logging.debug('RMS difference: %.3f', rms_diff)
-      e_msg = 'RMS difference: %.3f, spec: %.2f' % (
-          rms_diff, THRESHOLD_MAX_RMS_DIFF)
-      assert rms_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+      if rms_diff >= THRESHOLD_MAX_RMS_DIFF:
+        raise AssertionError(
+            f'RMS diff: {rms_diff:.3f}, spec: {THRESHOLD_MAX_RMS_DIFF}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_latching.py b/apps/CameraITS/tests/scene1_1/test_latching.py
index 158bb0f..6ebb405 100644
--- a/apps/CameraITS/tests/scene1_1/test_latching.py
+++ b/apps/CameraITS/tests/scene1_1/test_latching.py
@@ -90,7 +90,7 @@
         elif req_type == 'iso':
           reqs.append(iso_mult_req)
         else:
-          assert 0, 'Incorrect capture request!'
+          raise AssertionError(f'Incorrect capture request! {req_type}')
 
       caps = cam.do_capture(reqs, fmt)
       for i, cap in enumerate(caps):
@@ -121,8 +121,8 @@
       # check G mean pattern for correctness
       g_avg_for_caps = sum(g_means) / len(g_means)
       g_high = [g / g_avg_for_caps > 1 for g in g_means]
-      assert g_high == PATTERN_CHECK, 'G means: %s, TEMPLATE: %s' % (
-          str(g_means), str(REQ_PATTERN))
+      if g_high != PATTERN_CHECK:
+        raise AssertionError(f'G means: {g_means}, TEMPLATE: {REQ_PATTERN}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_linearity.py b/apps/CameraITS/tests/scene1_1/test_linearity.py
index 885311b..33baa52 100644
--- a/apps/CameraITS/tests/scene1_1/test_linearity.py
+++ b/apps/CameraITS/tests/scene1_1/test_linearity.py
@@ -126,9 +126,11 @@
             range(len(sensitivities)), means, 1, full=True)
         logging.debug('Line: m=%f, b=%f, resid=%f',
                       line[0], line[1], residuals[0])
-        msg = 'residual: %.5f, THRESH: %.4f' % (residuals[0], RESIDUAL_THRESH)
-        assert residuals[0] < RESIDUAL_THRESH, msg
-        assert line[0] > 0, 'slope %.6f less than 0!' % line[0]
+        if residuals[0] > RESIDUAL_THRESH:
+          raise AssertionError(
+              'residual: {residuals[0]:.5f}, THRESH: {RESIDUAL_THRESH}')
+        if line[0] <= 0:
+          raise AssertionError(f'slope {line[0]:.6f} <=  0!')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_locked_burst.py b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
index db72259..c586373 100644
--- a/apps/CameraITS/tests/scene1_1/test_locked_burst.py
+++ b/apps/CameraITS/tests/scene1_1/test_locked_burst.py
@@ -102,15 +102,15 @@
         logging.debug('%s patch mean spread %.5f. means = %s',
                       plane, spread, str(means))
         for j in range(BURST_LEN):
-          e_msg = '%s frame %d too dark! mean: %.5f, THRESH: %.2f' % (
-              plane, j, min_means, VALUE_THRESH)
-          assert min_means > VALUE_THRESH, e_msg
+          if min_means <= VALUE_THRESH:
+            raise AssertionError(f'{plane} frame {j} too dark! mean: '
+                                 f'{min_means:.5f}, THRESH: {VALUE_THRESH}')
           threshold = SPREAD_THRESH
           if camera_properties_utils.manual_sensor(props):
             threshold = SPREAD_THRESH_MANUAL_SENSOR
-          e_msg = '%s center patch spread: %.5f, THRESH: %.2f' % (
-              plane, spread, threshold)
-          assert spread < threshold, e_msg
+          if spread >= threshold:
+            raise AssertionError(f'{plane} center patch spread: {spread:.5f}, '
+                                 f'THRESH: {threshold:.2f}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py b/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
deleted file mode 100644
index 6c8d0e8..0000000
--- a/apps/CameraITS/tests/scene1_1/test_multi_camera_match.py
+++ /dev/null
@@ -1,144 +0,0 @@
-# Copyright 2018 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Verifies sub-cameras have similar RGB values for gray patch."""
-
-
-import logging
-import os.path
-
-from mobly import test_runner
-import numpy as np
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-
-_NAME = os.path.splitext(os.path.basename(__file__))[0]
-_PATCH_H = 0.0625  # 1/16 x 1/16 in center of image
-_PATCH_W = 0.0625
-_PATCH_X = 0.5 - _PATCH_W/2
-_PATCH_Y = 0.5 - _PATCH_H/2
-_THRESH_DIFF = 0.06
-_THRESH_GAIN = 0.1
-_THRESH_EXP = 0.05
-
-
-class MultiCameraMatchTest(its_base_test.ItsBaseTest):
-  """Test both cameras give similar RGB values for gray patch.
-
-  This test uses android.lens.info.availableFocalLengths to determine
-  subcameras. The test will take images of the gray chart for each cameras,
-  crop the center patch, and compare the Y (of YUV) means of the two images.
-  Y means must be within _THRESH_DIFF for the test to pass.
-
-  Cameras that use android.control.zoomRatioRange will have only 1 focal
-  length and will need separate test.
-  """
-
-  def test_multi_camera_match(self):
-    logging.debug('Starting %s', _NAME)
-    yuv_sizes = {}
-    with its_session_utils.ItsSession(
-        device_id=self.dut.serial,
-        camera_id=self.camera_id,
-        hidden_physical_id=self.hidden_physical_id) as cam:
-      props = cam.get_camera_properties()
-      props = cam.override_with_hidden_physical_camera_props(props)
-      log_path = self.log_path
-
-      # check SKIP conditions
-      camera_properties_utils.skip_unless(
-          camera_properties_utils.per_frame_control(props) and
-          camera_properties_utils.logical_multi_camera(props))
-
-      # Load chart for scene
-      its_session_utils.load_scene(
-          cam, props, self.scene, self.tablet, self.chart_distance)
-
-      ids = camera_properties_utils.logical_multi_camera_physical_ids(props)
-      for i in ids:
-        physical_props = cam.get_camera_properties_by_id(i)
-        camera_properties_utils.skip_unless(
-            not camera_properties_utils.mono_camera(physical_props) and
-            camera_properties_utils.backward_compatible(physical_props))
-        yuv_sizes[i] = capture_request_utils.get_available_output_sizes(
-            'yuv', physical_props)
-        if i == ids[0]:  # get_available_output_sizes returns sorted list
-          yuv_match_sizes = yuv_sizes[i]
-        else:
-          yuv_match_sizes = list(
-              set(yuv_sizes[i]).intersection(yuv_match_sizes))
-
-      # find matched size for captures
-      yuv_match_sizes.sort()
-      w = yuv_match_sizes[-1][0]
-      h = yuv_match_sizes[-1][1]
-      logging.debug('Matched YUV size: (%d, %d)', w, h)
-
-      # do 3a and create requests
-      cam.do_3a()
-      reqs = []
-      avail_fls = sorted(props['android.lens.info.availableFocalLengths'],
-                         reverse=True)
-      # SKIP test if only 1 focal length
-      camera_properties_utils.skip_unless(len(avail_fls) > 1)
-
-      for i, fl in enumerate(avail_fls):
-        reqs.append(capture_request_utils.auto_capture_request())
-        reqs[i]['android.lens.focalLength'] = fl
-        if i > 0:
-          # Calculate the active sensor region for a non-cropped image
-          zoom = avail_fls[0] / fl
-          aa = props['android.sensor.info.activeArraySize']
-          aa_w, aa_h = aa['right'] - aa['left'], aa['bottom'] - aa['top']
-
-          # Calculate a center crop region.
-          assert zoom >= 1
-          crop_w = aa_w // zoom
-          crop_h = aa_h // zoom
-          crop_region = {'left': aa_w // 2 - crop_w // 2,
-                         'top': aa_h // 2 - crop_h // 2,
-                         'right': aa_w // 2 + crop_w // 2,
-                         'bottom': aa_h // 2 + crop_h // 2}
-          reqs[i]['android.scaler.cropRegion'] = crop_region
-
-      # capture YUVs
-      y_means = {}
-      e_msg = ''
-      fmt = [{'format': 'yuv', 'width': w, 'height': h}]
-      caps = cam.do_capture(reqs, fmt)
-      for i, fl in enumerate(avail_fls):
-        img = image_processing_utils.convert_capture_to_rgb_image(
-            caps[i], props=props)
-        image_processing_utils.write_image(img, '%s_yuv_fl=%s.jpg' % (
-            os.path.join(log_path, _NAME), fl))
-        y, _, _ = image_processing_utils.convert_capture_to_planes(
-            caps[i], props=props)
-        y_mean = image_processing_utils.compute_image_means(
-            image_processing_utils.get_image_patch(
-                y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H))[0]
-        msg = 'y[%s]: %.3f, ' % (fl, y_mean)
-        logging.debug(msg)
-        e_msg += msg
-        y_means[fl] = y_mean
-
-      # compare Y means
-      e_msg += 'TOL=%.5f' % _THRESH_DIFF
-      assert np.isclose(max(y_means.values()), min(y_means.values()),
-                        rtol=_THRESH_DIFF), e_msg
-
-if __name__ == '__main__':
-  test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
index 7000bc2..446003e 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_exposure_time.py
@@ -101,8 +101,9 @@
     # Assert each shot is brighter than previous.
     for ch, means in enumerate([r_means, g_means, b_means]):
       for i in range(len(EXP_MULT_FACTORS)-1):
-        e_msg = '%s [i+1]: %.4f, [i]: %.4f' % (COLORS[ch], means[i+1], means[i])
-        assert means[i+1] > means[i], e_msg
+        if means[i+1] <= means[i]:
+          raise AssertionError(
+              f'{COLORS[ch]} [i+1]: {means[i+1]:.4f}, [i]: {means[i]:.4f}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
index 601b7fb..fca0f7d 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_flash_mode.py
@@ -108,19 +108,23 @@
       # Assert state behavior
       logging.debug('Reported modes: %s', str(modes))
       logging.debug('Reported states: %s', str(states))
-      assert modes == list(FLASH_MODES.values()), str(modes)
+      if modes != list(FLASH_MODES.values()):
+        raise AssertionError(f'modes != FLASH_MODES! {modes}')
 
-      e_msg = 'flash state reported[OFF]: %d' % states[FLASH_MODES['OFF']]
-      assert states[FLASH_MODES['OFF']] not in [
-          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+      if states[FLASH_MODES['OFF']] in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+        raise AssertionError('flash state reported[OFF]: '
+                             f"{states[FLASH_MODES['OFF']]}")
 
-      e_msg = 'flash state reported[SINGLE]: %d' % states[FLASH_MODES['SINGLE']]
-      assert states[FLASH_MODES['SINGLE']] in [
-          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+      if states[FLASH_MODES['SINGLE']] not in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+        raise AssertionError('flash state reported[SINGLE]: '
+                             f"{states[FLASH_MODES['SINGLE']]}")
 
-      e_msg = 'flash state reported[TORCH]: %d' % states[FLASH_MODES['TORCH']]
-      assert states[FLASH_MODES['TORCH']] in [
-          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']], e_msg
+      if states[FLASH_MODES['TORCH']] not in [
+          FLASH_STATES['FIRED'], FLASH_STATES['PARTIAL']]:
+        raise AssertionError('flash state reported[TORCH]: '
+                             f"{states[FLASH_MODES['TORCH']]}")
 
       # Assert image behavior: change between OFF & SINGLE
       logging.debug('Brightness means: %s', str(means))
@@ -128,23 +132,23 @@
       grad_delta = grads[FLASH_MODES['SINGLE']] - grads[FLASH_MODES['OFF']]
       mean_delta = ((means[FLASH_MODES['SINGLE']] - means[FLASH_MODES['OFF']]) /
                     means[FLASH_MODES['OFF']])
-      e_msg = 'gradient SINGLE-OFF: %.3f, ATOL: %.3f' % (
-          grad_delta, GRADIENT_DELTA)
-      e_msg += ' mean SINGLE:OFF %.3f, ATOL: %.3f' % (
-          mean_delta, Y_RELATIVE_DELTA_FLASH)
-      assert (grad_delta > GRADIENT_DELTA or
-              mean_delta > Y_RELATIVE_DELTA_FLASH), e_msg
+      if not (grad_delta > GRADIENT_DELTA or
+              mean_delta > Y_RELATIVE_DELTA_FLASH):
+        raise AssertionError(f'gradient SINGLE-OFF: {grad_delta:.3f}, '
+                             f'ATOL: {GRADIENT_DELTA}, '
+                             f'mean SINGLE:OFF {mean_delta:.3f}, '
+                             f'ATOL: {Y_RELATIVE_DELTA_FLASH}')
 
       # Assert image behavior: change between OFF & TORCH
       grad_delta = grads[FLASH_MODES['TORCH']] - grads[FLASH_MODES['OFF']]
       mean_delta = ((means[FLASH_MODES['TORCH']] - means[FLASH_MODES['OFF']]) /
                     means[FLASH_MODES['OFF']])
-      e_msg = 'gradient TORCH-OFF: %.3f, ATOL: %.3f' % (
-          grad_delta, GRADIENT_DELTA)
-      e_msg += ' mean TORCH:OFF %.3f, ATOL: %.3f' % (
-          mean_delta, Y_RELATIVE_DELTA_TORCH)
-      assert (grad_delta > GRADIENT_DELTA or
-              mean_delta > Y_RELATIVE_DELTA_TORCH), e_msg
+      if not (grad_delta > GRADIENT_DELTA or
+              mean_delta > Y_RELATIVE_DELTA_TORCH):
+        raise AssertionError(f'gradient TORCH-OFF: {grad_delta:.3f}, '
+                             f'ATOL: {GRADIENT_DELTA}, '
+                             f'mean TORCH:OFF {mean_delta:.3f}, '
+                             f'ATOL: {Y_RELATIVE_DELTA_TORCH}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
index b605757..6323a4a 100644
--- a/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1_1/test_param_noise_reduction.py
@@ -143,58 +143,62 @@
     pylab.xticks(NR_MODES_LIST)
     matplotlib.pyplot.savefig('%s_plot_SNRs.png' % os.path.join(log_path, NAME))
 
-    assert nr_modes_reported == NR_MODES_LIST
+    if nr_modes_reported != NR_MODES_LIST:
+      raise AssertionError(f'{nr_modes_reported} != {NR_MODES_LIST}')
 
     for j in range(NUM_COLORS):
       # Higher SNR is better
       # Verify OFF is not better than FAST
-      e_msg = '%s OFF: %.3f, FAST: %.3f, TOL: %.3f' % (
-          COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['FAST']],
-          SNR_TOLERANCE)
-      assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['FAST']] +
-              SNR_TOLERANCE), e_msg
+      if (snrs[j][NR_MODES['OFF']] >= snrs[j][NR_MODES['FAST']] +
+          SNR_TOLERANCE):
+        raise AssertionError(
+            f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+            f"FAST: {snrs[j][NR_MODES['FAST']]:.3f}, TOL: {SNR_TOLERANCE}")
 
       # Verify FAST is not better than HQ
-      e_msg = '%s FAST: %.3f, HQ: %.3f, TOL: %.3f' % (
-          COLORS[j], snrs[j][NR_MODES['FAST']], snrs[j][NR_MODES['HQ']],
-          SNR_TOLERANCE)
-      assert (snrs[j][NR_MODES['FAST']] < snrs[j][NR_MODES['HQ']] +
-              SNR_TOLERANCE), e_msg
+      if (snrs[j][NR_MODES['FAST']] >= snrs[j][NR_MODES['HQ']] +
+          SNR_TOLERANCE):
+        raise AssertionError(
+            f"{COLORS[j]} FAST: {snrs[j][NR_MODES['FAST']]:.3f}, "
+            f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}, TOL: {SNR_TOLERANCE}")
 
       # Verify HQ is better than OFF
-      e_msg = '%s OFF: %.3f, HQ: %.3f' % (
-          COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['HQ']])
-      assert snrs[j][NR_MODES['HQ']] > snrs[j][NR_MODES['OFF']], e_msg
+      if snrs[j][NR_MODES['HQ']] <= snrs[j][NR_MODES['OFF']]:
+        raise AssertionError(
+            f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+            f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}")
 
       if camera_properties_utils.noise_reduction_mode(props, NR_MODES['MIN']):
         # Verify OFF is not better than MINIMAL
-        e_msg = '%s OFF: %.3f, MIN: %.3f, TOL: %.3f' % (
-            COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['MIN']],
-            SNR_TOLERANCE)
-        assert (snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['MIN']] +
-                SNR_TOLERANCE), e_msg
+        if not(snrs[j][NR_MODES['OFF']] < snrs[j][NR_MODES['MIN']] +
+               SNR_TOLERANCE):
+          raise AssertionError(
+              f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:.3f}, "
+              f"MIN: {snrs[j][NR_MODES['MIN']]:.3f}, TOL: {SNR_TOLERANCE}")
 
         # Verify MINIMAL is not better than HQ
-        e_msg = '%s MIN: %.3f, HQ: %.3f, TOL: %.3f' % (
-            COLORS[j], snrs[j][NR_MODES['MIN']], snrs[j][NR_MODES['HQ']],
-            SNR_TOLERANCE)
-        assert (snrs[j][NR_MODES['MIN']] < snrs[j][NR_MODES['HQ']] +
-                SNR_TOLERANCE), e_msg
+        if not (snrs[j][NR_MODES['MIN']] < snrs[j][NR_MODES['HQ']] +
+                SNR_TOLERANCE):
+          raise AssertionError(
+              f"{COLORS[j]} MIN: {snrs[j][NR_MODES['MIN']]:.3f}, "
+              f"HQ: {snrs[j][NR_MODES['HQ']]:.3f}, TOL: {SNR_TOLERANCE}")
 
+        # Verify ZSL is close to MINIMAL
         if camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
-          # Verify ZSL is close to MINIMAL
-          e_msg = '%s ZSL: %.3f, MIN: %.3f, TOL: %.3f' % (
-              COLORS[j], snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
-              SNR_TOLERANCE)
-          assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
-                            atol=SNR_TOLERANCE), e_msg
+          if not np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['MIN']],
+                            atol=SNR_TOLERANCE):
+            raise AssertionError(
+                f"{COLORS[j]} ZSL: {snrs[j][NR_MODES['ZSL']]:.3f}, "
+                f"MIN: {snrs[j][NR_MODES['MIN']]:.3f}, TOL: {SNR_TOLERANCE}")
+
       elif camera_properties_utils.noise_reduction_mode(props, NR_MODES['ZSL']):
         # Verify ZSL is close to OFF
-        e_msg = '%s OFF: %.3f, ZSL: %.3f, TOL: %.3f' % (
-            COLORS[j], snrs[j][NR_MODES['OFF']], snrs[j][NR_MODES['ZSL']],
-            SNR_TOLERANCE)
-        assert np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['OFF']],
-                          atol=SNR_TOLERANCE), e_msg
+        if not np.isclose(snrs[j][NR_MODES['ZSL']], snrs[j][NR_MODES['OFF']],
+                          atol=SNR_TOLERANCE):
+          raise AssertionError(
+              f"{COLORS[j]} OFF: {snrs[j][NR_MODES['OFF']]:3f}, "
+              f"ZSL: {snrs[j][NR_MODES['ZSL']]:3f}, TOL: {SNR_TOLERANCE}")
+
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
index 4c6af25..4cc4546 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_burst_sensitivity.py
@@ -77,9 +77,9 @@
           camera_properties_utils.per_frame_control(props) and
           not camera_properties_utils.mono_camera(props))
 
-      # Load chart for scene
+      # Load chart for scene (chart_distance=0 for no chart scaling)
       its_session_utils.load_scene(
-          cam, props, self.scene, self.tablet, self.chart_distance)
+          cam, props, self.scene, self.tablet, chart_distance=0)
 
       # Find sensitivity range and create capture requests
       sens_min, _ = props['android.sensor.info.sensitivityRange']
@@ -130,9 +130,10 @@
 
       # Asserts that each shot is noisier than previous
       for i in x[0:-1]:
-        e_msg = 'variances [i]: %.5f, [i+1]: %.5f, THRESH: %.2f' % (
-            variances[i], variances[i+1], _VAR_THRESH)
-        assert variances[i] < variances[i+1] / _VAR_THRESH, e_msg
+        if variances[i] >= variances[i+1] / _VAR_THRESH:
+          raise AssertionError(
+              f'variances [i]: {variances[i] :.5f}, [i+1]: '
+              f'{variances[i+1]:.5f}, THRESH: {_VAR_THRESH}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
index 985cf12..8c542ab 100644
--- a/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
+++ b/apps/CameraITS/tests/scene1_2/test_raw_sensitivity.py
@@ -65,9 +65,9 @@
       name_with_log_path = os.path.join(self.log_path, NAME)
       camera_fov = float(cam.calc_camera_fov(props))
 
-      # Load chart for scene
+      # Load chart for scene (chart_distance=0 for no chart scaling)
       its_session_utils.load_scene(
-          cam, props, self.scene, self.tablet, self.chart_distance)
+          cam, props, self.scene, self.tablet, chart_distance=0)
 
       # Expose for the scene with min sensitivity
       sens_min, _ = props['android.sensor.info.sensitivityRange']
diff --git a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
index b1f1b8a..214dc04 100644
--- a/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
+++ b/apps/CameraITS/tests/scene1_2/test_yuv_jpeg_all.py
@@ -52,21 +52,28 @@
   out_surface = {'width': size[0], 'height': size[1], 'format': img_type}
   cap = cam.do_capture(req, out_surface)
   if img_type == 'jpg':
-    assert cap['format'] == 'jpeg'
+    if cap['format'] != 'jpeg':
+      raise AssertionError(f"{cap['format']} != jpeg")
     img = image_processing_utils.decompress_jpeg_to_rgb_image(cap['data'])
   else:
-    assert cap['format'] == img_type
+    if cap['format'] != img_type:
+      raise AssertionError(f"{cap['format']} != {img_type}")
     img = image_processing_utils.convert_capture_to_rgb_image(cap)
-  assert cap['width'] == size[0]
-  assert cap['height'] == size[1]
+  if cap['width'] != size[0]:
+    raise AssertionError(f"{cap['width']} != {size[0]}")
+  if cap['height'] != size[1]:
+    raise AssertionError(f"{cap['height']} != {size[1]}")
 
   if debug:
     image_processing_utils.write_image(img, '%s_%s_w%d_h%d.jpg'%(
         os.path.join(log_path, NAME), img_type, size[0], size[1]))
   if img_type == 'jpg':
-    assert img.shape[0] == size[1]
-    assert img.shape[1] == size[0]
-    assert img.shape[2] == 3
+    if img.shape[0] != size[1]:
+      raise AssertionError(f'{img.shape[0]} != {size[1]}')
+    if img.shape[1] != size[0]:
+      raise AssertionError(f'{img.shape[1]} != {size[0]}')
+    if img.shape[2] != 3:
+      raise AssertionError(f'{img.shape[2]} != 3')
   patch = image_processing_utils.get_image_patch(
       img, PATCH_X, PATCH_Y, PATCH_W, PATCH_H)
   rgb = image_processing_utils.compute_image_means(patch)
@@ -133,8 +140,8 @@
         max_diff = max(max_diff, rms_diff)
       msg = 'Max RMS difference: %.4f' % max_diff
       logging.debug('%s', msg)
-      e_msg = msg + ' spec: %.3f' % THRESHOLD_MAX_RMS_DIFF
-      assert max_diff < THRESHOLD_MAX_RMS_DIFF, e_msg
+      if max_diff >= THRESHOLD_MAX_RMS_DIFF:
+        raise AssertionError(f'{msg} spec: {THRESHOLD_MAX_RMS_DIFF}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_a/test_auto_flash.py b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
new file mode 100644
index 0000000..c6d0633
--- /dev/null
+++ b/apps/CameraITS/tests/scene2_a/test_auto_flash.py
@@ -0,0 +1,177 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Verifies android.flash.mode parameters is applied when set."""
+
+
+import logging
+import os.path
+from mobly import test_runner
+
+import its_base_test
+import camera_properties_utils
+import capture_request_utils
+import lighting_control_utils
+import image_processing_utils
+import its_session_utils
+
+AE_MODES = {0: 'OFF', 1: 'ON', 2: 'ON_AUTO_FLASH', 3: 'ON_ALWAYS_FLASH',
+            4: 'ON_AUTO_FLASH_REDEYE', 5: 'ON_EXTERNAL_FLASH'}
+AE_STATES = {0: 'INACTIVE', 1: 'SEARCHING', 2: 'CONVERGED', 3: 'LOCKED',
+             4: 'FLASH_REQUIRED', 5: 'PRECAPTURE'}
+_FLASH_CHECK_FIRST_API_LEVEL = 32
+_GRAD_DELTA_ATOL = 100  # gradiant for tablets as screen aborbs energy
+_MEAN_DELTA_ATOL = 100  # mean used for reflective charts
+_NUM_FRAMES = 8
+_PATCH_H = 0.25  # center 25%
+_PATCH_W = 0.25
+_PATCH_X = 0.5 - _PATCH_W/2
+_PATCH_Y = 0.5 - _PATCH_H/2
+_TEST_NAME = os.path.splitext(os.path.basename(__file__))[0]
+VGA_W, VGA_H = 640, 480
+
+
+def take_captures(cam, auto_flash=False):
+  req = capture_request_utils.auto_capture_request()
+  if auto_flash:
+    req['android.control.aeMode'] = 2  # 'ON_AUTO_FLASH'
+  fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
+  return cam.do_capture([req]*_NUM_FRAMES, fmt)
+
+
+class AutoFlashTest(its_base_test.ItsBaseTest):
+  """Test that the android.flash.mode parameter is applied."""
+
+  def test_auto_flash(self):
+    logging.debug('AE_MODES: %s', str(AE_MODES))
+    logging.debug('AE_STATES: %s', str(AE_STATES))
+
+    with its_session_utils.ItsSession(
+        device_id=self.dut.serial,
+        camera_id=self.camera_id,
+        hidden_physical_id=self.hidden_physical_id) as cam:
+      props = cam.get_camera_properties()
+      props = cam.override_with_hidden_physical_camera_props(props)
+      test_name = os.path.join(self.log_path, _TEST_NAME)
+
+      # check SKIP conditions
+      first_api_level = its_session_utils.get_first_api_level(self.dut.serial)
+      camera_properties_utils.skip_unless(
+          camera_properties_utils.flash(props) and
+          first_api_level >= _FLASH_CHECK_FIRST_API_LEVEL)
+
+      # establish connection with lighting controller
+      arduino_serial_port = lighting_control_utils.lighting_control(
+          self.lighting_cntl, self.lighting_ch)
+
+      # turn OFF lights to darken scene
+      lighting_control_utils.set_lighting_state(
+          arduino_serial_port, self.lighting_ch, 'OFF')
+
+      # turn OFF tablet to darken scene
+      if self.tablet:
+        output = self.tablet.adb.shell('dumpsys display | grep mScreenState')
+        output_list = str(output.decode('utf-8')).strip().split(' ')
+        for val in output_list:
+          if 'ON' in val:
+            self.tablet.adb.shell(['input', 'keyevent', 'KEYCODE_POWER'])
+
+      no_flash_exp_x_iso = 0
+      no_flash_mean = 0
+      no_flash_grad = 0
+      flash_exp_x_iso = []
+      flash_means = []
+      flash_grads = []
+
+      # take captures with no flash as baseline: use last frame
+      logging.debug('Taking reference frame(s) with no flash.')
+      cam.do_3a(do_af=False)
+      cap = take_captures(cam)[_NUM_FRAMES-1]
+      metadata = cap['metadata']
+      exp = int(metadata['android.sensor.exposureTime'])
+      iso = int(metadata['android.sensor.sensitivity'])
+      logging.debug('No auto_flash ISO: %d, exp: %d ns', iso, exp)
+      logging.debug('AE_MODE (cap): %s',
+                    AE_MODES[metadata['android.control.aeMode']])
+      logging.debug('AE_STATE (cap): %s',
+                    AE_STATES[metadata['android.control.aeState']])
+      no_flash_exp_x_iso = exp * iso
+      y, _, _ = image_processing_utils.convert_capture_to_planes(
+          cap, props)
+      patch = image_processing_utils.get_image_patch(
+          y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+      no_flash_mean = image_processing_utils.compute_image_means(
+          patch)[0]*255
+      no_flash_grad = image_processing_utils.compute_image_max_gradients(
+          patch)[0]*255
+      image_processing_utils.write_image(y, f'{test_name}_no_flash_Y.jpg')
+
+      # log results
+      logging.debug('No flash exposure X ISO %d', no_flash_exp_x_iso)
+      logging.debug('No flash Y grad: %.4f', no_flash_grad)
+      logging.debug('No flash Y mean: %.4f', no_flash_mean)
+
+      # take captures with auto flash enabled
+      logging.debug('Taking frames with auto flash enabled.')
+      cam.do_3a(do_af=False, auto_flash=True)
+      caps = take_captures(cam, auto_flash=True)
+
+      # evaluate captured images
+      for i in range(_NUM_FRAMES):
+        logging.debug('frame # %d', i)
+        metadata = caps[i]['metadata']
+        exp = int(metadata['android.sensor.exposureTime'])
+        iso = int(metadata['android.sensor.sensitivity'])
+        logging.debug('ISO: %d, exp: %d ns', iso, exp)
+        if i == 0:
+          logging.debug('AE_MODE (cap): %s',
+                        AE_MODES[metadata['android.control.aeMode']])
+        ae_state = AE_STATES[metadata['android.control.aeState']]
+        logging.debug('AE_STATE (cap): %s', ae_state)
+        if ae_state != AE_STATES[4]:  # FLASH_REQUIRED
+          raise AssertionError('Scene not dark enough to trigger auto-flash. '
+                               'Check scene.')
+        flash_exp_x_iso.append(exp*iso)
+        y, _, _ = image_processing_utils.convert_capture_to_planes(
+            caps[i], props)
+        patch = image_processing_utils.get_image_patch(
+            y, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
+        flash_means.append(
+            image_processing_utils.compute_image_means(patch)[0]*255)
+        flash_grads.append(
+            image_processing_utils.compute_image_max_gradients(patch)[0]*255)
+
+        image_processing_utils.write_image(
+            y, f'{test_name}_auto_flash_Y_{i}.jpg')
+
+      # log results
+      logging.debug('Flash exposure X ISOs %s', str(flash_exp_x_iso))
+      logging.debug('Flash frames Y grads: %s', str(flash_grads))
+      logging.debug('Flash frames Y means: %s', str(flash_means))
+
+      # turn lights back ON
+      lighting_control_utils.set_lighting_state(
+          arduino_serial_port, self.lighting_ch, 'ON')
+
+      # assert correct behavior
+      grad_delta = max(flash_grads) - no_flash_grad
+      mean_delta = max(flash_means) - no_flash_mean
+      if not (grad_delta > _GRAD_DELTA_ATOL or
+              mean_delta > _MEAN_DELTA_ATOL):
+        raise AssertionError(
+            f'grad FLASH-OFF: {grad_delta:.3f}, ATOL: {_GRAD_DELTA_ATOL}, '
+            f'mean FLASH-OFF: {mean_delta:.3f}, ATOL: {_MEAN_DELTA_ATOL}')
+
+if __name__ == '__main__':
+  test_runner.main()
+
diff --git a/apps/CameraITS/tests/scene2_a/test_faces.py b/apps/CameraITS/tests/scene2_a/test_faces.py
deleted file mode 100644
index 220628d..0000000
--- a/apps/CameraITS/tests/scene2_a/test_faces.py
+++ /dev/null
@@ -1,179 +0,0 @@
-# Copyright 2014 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Verifies faces are detected and landmarks in bounding boxes."""
-
-
-import logging
-import os.path
-from mobly import test_runner
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-
-NAME = os.path.splitext(os.path.basename(__file__))[0]
-NUM_TEST_FRAMES = 20
-FD_MODE_OFF = 0
-FD_MODE_SIMPLE = 1
-FD_MODE_FULL = 2
-W, H = 640, 480
-
-
-def check_face_bounding_box(rect, aa_w, aa_h):
-  """Check that face bounding box is within the active array area."""
-  rect_t = rect['top']
-  rect_b = rect['bottom']
-  rect_l = rect['left']
-  rect_r = rect['right']
-  if rect_t > rect_b:
-    raise AssertionError(f'Face top > bottom! t: {rect_t}, b: {rect_b}')
-  if rect_l > rect_r:
-    raise AssertionError(f'Face left > right! l: {rect_l}, r: {rect_r}')
-
-  if not 0 <= rect_l <= aa_w:
-    raise AssertionError(f'Face l: {rect_l} outside of active W: 0,{aa_w}')
-  if not 0 <= rect_r <= aa_w:
-    raise AssertionError(f'Face r: {rect_r} outside of active W: 0,{aa_w}')
-  if not 0 <= rect_t <= aa_h:
-    raise AssertionError(f'Face t: {rect_t} outside active H: 0,{aa_h}')
-  if not 0 <= rect_b <= aa_h:
-    raise AssertionError(f'Face b: {rect_b} outside active H: 0,{aa_h}')
-
-
-def check_face_landmarks(face):
-  """Check that face landmarks fall within face bounding box."""
-  l, r = face['bounds']['left'], face['bounds']['right']
-  t, b = face['bounds']['top'], face['bounds']['bottom']
-  l_eye_x, l_eye_y = face['leftEye']['x'], face['leftEye']['y']
-  r_eye_x, r_eye_y = face['rightEye']['x'], face['rightEye']['y']
-  mouth_x, mouth_y = face['mouth']['x'], face['mouth']['y']
-  if not l <= l_eye_x <= r:
-    raise AssertionError(f'Face l: {l}, r: {r}, left eye x: {l_eye_x}')
-  if not t <= l_eye_y <= b:
-    raise AssertionError(f'Face t: {t}, b: {b}, left eye y: {l_eye_y}')
-  if not l <= r_eye_x <= r:
-    raise AssertionError(f'Face l: {l}, r: {r}, right eye x: {r_eye_x}')
-  if not t <= r_eye_y <= b:
-    raise AssertionError(f'Face t: {t}, b: {b}, right eye y: {r_eye_y}')
-  if not l <= mouth_x <= r:
-    raise AssertionError(f'Face l: {l}, r: {r}, mouth x: {mouth_x}')
-  if not t <= mouth_y <= b:
-    raise AssertionError(f'Face t: {t}, b: {b}, mouth y: {mouth_y}')
-
-
-class FacesTest(its_base_test.ItsBaseTest):
-  """Tests face detection algorithms.
-
-  Allows NUM_TEST_FRAMES for face detection algorithm to find all faces.
-  Tests OFF, SIMPLE, and FULL modes if available.
-    OFF --> no faces should be found.
-    SIMPLE --> face(s) should be found, but no landmarks.
-    FULL --> face(s) should be found and face landmarks reported.
-  """
-
-  def test_faces(self):
-    logging.debug('Starting %s', NAME)
-    with its_session_utils.ItsSession(
-        device_id=self.dut.serial,
-        camera_id=self.camera_id,
-        hidden_physical_id=self.hidden_physical_id) as cam:
-      props = cam.get_camera_properties()
-      props = cam.override_with_hidden_physical_camera_props(props)
-
-      # Load chart for scene.
-      its_session_utils.load_scene(
-          cam, props, self.scene, self.tablet, self.chart_distance)
-
-      camera_properties_utils.skip_unless(
-          camera_properties_utils.face_detect(props))
-      mono_camera = camera_properties_utils.mono_camera(props)
-      fd_modes = props['android.statistics.info.availableFaceDetectModes']
-      a = props['android.sensor.info.activeArraySize']
-      aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
-      if camera_properties_utils.read_3a(props):
-        gain, exp, _, _, focus = cam.do_3a(
-            get_results=True, mono_camera=mono_camera)
-        logging.debug('iso = %d', gain)
-        logging.debug('exp = %.2fms', (exp * 1.0E-6))
-        if focus == 0.0:
-          logging.debug('fd = infinity')
-        else:
-          logging.debug('fd = %.2fcm', (1.0E2 / focus))
-      for fd_mode in fd_modes:
-        if not FD_MODE_OFF <= fd_mode <= FD_MODE_FULL:
-          raise AssertionError(f'fd_mode undefined: {fd_mode}')
-        req = capture_request_utils.auto_capture_request()
-        req['android.statistics.faceDetectMode'] = fd_mode
-        fmt = {'format': 'yuv', 'width': W, 'height': H}
-        caps = cam.do_capture([req] * NUM_TEST_FRAMES, fmt)
-        for i, cap in enumerate(caps):
-          fd_mode_md = cap['metadata']['android.statistics.faceDetectMode']
-          if fd_mode_md != fd_mode:
-            raise AssertionError('Metadata does not match request! '
-                                 f'Request: {fd_mode} metadata: {fd_mode_md}.')
-          faces = cap['metadata']['android.statistics.faces']
-
-          # 0 faces should be returned for OFF mode
-          if fd_mode == FD_MODE_OFF:
-            if faces:
-              raise AssertionError('Faces found in OFF mode.')
-            continue
-          # Save last frame.
-          if i == NUM_TEST_FRAMES - 1:
-            img = image_processing_utils.convert_capture_to_rgb_image(
-                cap, props=props)
-            img = image_processing_utils.rotate_img_per_argv(img)
-            img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
-                                                           NAME), fd_mode)
-            image_processing_utils.write_image(img, img_name)
-            if not faces:
-              raise AssertionError(f'No face detected in mode {fd_mode}.')
-          if not faces:
-            continue
-
-          logging.debug('Frame %d face metadata:', i)
-          logging.debug('Faces: %s', faces)
-
-          face_scores = [face['score'] for face in faces]
-          face_rectangles = [face['bounds'] for face in faces]
-          for score in face_scores:
-            if not 1 <= score <= 100:
-              raise AssertionError(f'Face score not valid! score: {score}.')
-          # Face bounds should be within active array.
-          for j, rect in enumerate(face_rectangles):
-            logging.debug('Checking face rectangle %d', j)
-            check_face_bounding_box(rect, aw, ah)
-
-          # Face ID should be -1 for SIMPLE and unique for FULL
-          if fd_mode == FD_MODE_SIMPLE:
-            for face in faces:
-              if 'leftEye' in face or 'rightEye' in face:
-                raise AssertionError('Eyes not supported in FD_MODE_SIMPLE.')
-              if 'mouth' in face:
-                raise AssertionError('Mouth not supported in FD_MODE_SIMPLE.')
-              if face['id'] != -1:
-                raise AssertionError('face_id should be -1 in FD_MODE_SIMPLE.')
-          elif fd_mode == FD_MODE_FULL:
-            face_ids = [face['id'] for face in faces]
-            if len(face_ids) != len(set(face_ids)):
-              raise AssertionError('Same face detected more than 1x.')
-            # Face landmarks should be within face bounds
-            for k, face in enumerate(faces):
-              logging.debug('Checking landmarks in face %d: %s', k, str(face))
-              check_face_landmarks(face)
-
-if __name__ == '__main__':
-  test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_a/test_num_faces.py b/apps/CameraITS/tests/scene2_a/test_num_faces.py
index 9cdca32..304b67b 100644
--- a/apps/CameraITS/tests/scene2_a/test_num_faces.py
+++ b/apps/CameraITS/tests/scene2_a/test_num_faces.py
@@ -34,22 +34,90 @@
 W, H = 640, 480
 
 
-def draw_face_rectangles(img, faces, aw, ah):
+def check_face_bounding_box(rect, aw, ah, index):
+  """Checks face bounding box is within the active array area.
+
+  Args:
+    rect: dict; with face bounding box information
+    aw: int; active array width
+    ah: int; active array height
+    index: int to designate face number
+  """
+  logging.debug('Checking bounding box in face %d: %s', index, str(rect))
+  if (rect['top'] >= rect['bottom'] or
+      rect['left'] >= rect['right']):
+    raise AssertionError('Face coordinates incorrect! '
+                         f" t: {rect['top']}, b: {rect['bottom']}, "
+                         f" l: {rect['left']}, r: {rect['right']}")
+  if (not 0 <= rect['top'] <= ah or
+      not 0 <= rect['bottom'] <= ah):
+    raise AssertionError('Face top/bottom outside of image height! '
+                         f"t: {rect['top']}, b: {rect['bottom']}, "
+                         f"h: {ah}")
+  if (not 0 <= rect['left'] <= aw or
+      not 0 <= rect['right'] <= aw):
+    raise AssertionError('Face left/right outside of image width! '
+                         f"l: {rect['left']}, r: {rect['right']}, "
+                         f" w: {aw}")
+
+
+def check_face_landmarks(face, fd_mode, index):
+  """Checks face landmarks fall within face bounding box.
+
+  Face ID should be -1 for SIMPLE and unique for FULL
+  Args:
+    face: dict from face detection algorithm
+    fd_mode: int of face detection mode
+    index: int to designate face number
+  """
+  logging.debug('Checking landmarks in face %d: %s', index, str(face))
+  if fd_mode == FD_MODE_SIMPLE:
+    if 'leftEye' in face or 'rightEye' in face:
+      raise AssertionError('Eyes not supported in FD_MODE_SIMPLE.')
+    if 'mouth' in face:
+      raise AssertionError('Mouth not supported in FD_MODE_SIMPLE.')
+    if face['id'] != -1:
+      raise AssertionError('face_id should be -1 in FD_MODE_SIMPLE.')
+  elif fd_mode == FD_MODE_FULL:
+    l, r = face['bounds']['left'], face['bounds']['right']
+    t, b = face['bounds']['top'], face['bounds']['bottom']
+    l_eye_x, l_eye_y = face['leftEye']['x'], face['leftEye']['y']
+    r_eye_x, r_eye_y = face['rightEye']['x'], face['rightEye']['y']
+    mouth_x, mouth_y = face['mouth']['x'], face['mouth']['y']
+    if not l <= l_eye_x <= r:
+      raise AssertionError(f'Face l: {l}, r: {r}, left eye x: {l_eye_x}')
+    if not t <= l_eye_y <= b:
+      raise AssertionError(f'Face t: {t}, b: {b}, left eye y: {l_eye_y}')
+    if not l <= r_eye_x <= r:
+      raise AssertionError(f'Face l: {l}, r: {r}, right eye x: {r_eye_x}')
+    if not t <= r_eye_y <= b:
+      raise AssertionError(f'Face t: {t}, b: {b}, right eye y: {r_eye_y}')
+    if not l <= mouth_x <= r:
+      raise AssertionError(f'Face l: {l}, r: {r}, mouth x: {mouth_x}')
+    if not t <= mouth_y <= b:
+      raise AssertionError(f'Face t: {t}, b: {b}, mouth y: {mouth_y}')
+  else:
+    raise AssertionError(f'Unknown face detection mode: {fd_mode}.')
+
+
+def draw_face_rectangles(img, faces, crop):
   """Draw rectangles on top of image.
 
   Args:
     img:    image array
     faces:  list of dicts with face information
-    aw:     int; active array width
-    ah:     int; active array height
+    crop:   dict; crop region size with 'top, right, left, bottom' as keys
   Returns:
     img with face rectangles drawn on it
   """
+  cw, ch = crop['right'] - crop['left'], crop['bottom'] - crop['top']
+  logging.debug('crop region: %s', str(crop))
   for rect in [face['bounds'] for face in faces]:
-    top_left = (int(round(rect['left']*W/aw)),
-                int(round(rect['top']*H/ah)))
-    bot_rght = (int(round(rect['right']*W/aw)),
-                int(round(rect['bottom']*H/ah)))
+    logging.debug('rect: %s', str(rect))
+    top_left = (int(round((rect['left'] - crop['left']) * img.shape[1] / cw)),
+                int(round((rect['top'] - crop['top']) * img.shape[0] / ch)))
+    bot_rght = (int(round((rect['right'] - crop['left']) * img.shape[1] / cw)),
+                int(round((rect['bottom'] - crop['top']) * img.shape[0] / ch)))
     cv2.rectangle(img, top_left, bot_rght, (0, 1, 0), 2)
   return img
 
@@ -78,24 +146,31 @@
       fd_modes = props['android.statistics.info.availableFaceDetectModes']
       a = props['android.sensor.info.activeArraySize']
       aw, ah = a['right'] - a['left'], a['bottom'] - a['top']
+      logging.debug('active array size: %s', str(a))
+      file_name_stem = os.path.join(self.log_path, NAME)
 
       if camera_properties_utils.read_3a(props):
         _, _, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
 
       for fd_mode in fd_modes:
-        assert FD_MODE_OFF <= fd_mode <= FD_MODE_FULL
+        logging.debug('face detection mode: %d', fd_mode)
+        if not FD_MODE_OFF <= fd_mode <= FD_MODE_FULL:
+          raise AssertionError(f'FD mode {fd_mode} not in MODES! '
+                               f'OFF: {FD_MODE_OFF}, FULL: {FD_MODE_FULL}')
         req = capture_request_utils.auto_capture_request()
         req['android.statistics.faceDetectMode'] = fd_mode
         fmt = {'format': 'yuv', 'width': W, 'height': H}
         caps = cam.do_capture([req]*NUM_TEST_FRAMES, fmt)
         for i, cap in enumerate(caps):
-          md = cap['metadata']
-          assert md['android.statistics.faceDetectMode'] == fd_mode
-          faces = md['android.statistics.faces']
+          fd_mode_cap = cap['metadata']['android.statistics.faceDetectMode']
+          if fd_mode_cap != fd_mode:
+            raise AssertionError(f'metadata {fd_mode_cap} != req {fd_mode}')
 
+          faces = cap['metadata']['android.statistics.faces']
           # 0 faces should be returned for OFF mode
           if fd_mode == FD_MODE_OFF:
-            assert not faces
+            if faces:
+              raise AssertionError(f'Error: faces detected in OFF: {faces}')
             continue
           # Face detection could take several frames to warm up,
           # but should detect the correct number of faces in last frame
@@ -106,12 +181,14 @@
             logging.debug('Found %d face(s), expected %d.',
                           fnd_faces, NUM_FACES)
             # draw boxes around faces
-            img = draw_face_rectangles(img, faces, aw, ah)
+            crop_region = cap['metadata']['android.scaler.cropRegion']
+            img = draw_face_rectangles(img, faces, crop_region)
             # save image with rectangles
-            img_name = '%s_fd_mode_%s.jpg' % (os.path.join(self.log_path,
-                                                           NAME), fd_mode)
+            img_name = f'{file_name_stem}_fd_mode_{fd_mode}.jpg'
             image_processing_utils.write_image(img, img_name)
-            assert fnd_faces == NUM_FACES
+            if fnd_faces != NUM_FACES:
+              raise AssertionError('Wrong num of faces found! '
+                                   f'Found: {fnd_faces}, expected: {NUM_FACES}')
           if not faces:
             continue
 
@@ -121,16 +198,18 @@
           # Reasonable scores for faces
           face_scores = [face['score'] for face in faces]
           for score in face_scores:
-            assert 1 <= score <= 100
+            if not 1 <= score <= 100:
+              raise AssertionError(f'score not between [1:100]! {score}')
+
           # Face bounds should be within active array
           face_rectangles = [face['bounds'] for face in faces]
-          for rect in face_rectangles:
-            assert rect['top'] < rect['bottom']
-            assert rect['left'] < rect['right']
-            assert 0 <= rect['top'] <= ah
-            assert 0 <= rect['bottom'] <= ah
-            assert 0 <= rect['left'] <= aw
-            assert 0 <= rect['right'] <= aw
+          for j, rect in enumerate(face_rectangles):
+            check_face_bounding_box(rect, aw, ah, j)
+
+          # Face landmarks (if provided) are within face bounding box
+          for k, face in enumerate(faces):
+            check_face_landmarks(face, fd_mode, k)
+
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
index e1ed893..e7c7af8 100644
--- a/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_camera_launch_perf_class.py
@@ -22,13 +22,14 @@
 import its_base_test
 import its_session_utils
 
+# This must match MPC12_CAMERA_LAUNCH_THRESHOLD in ItsTestActivity.java
 CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD = 600  # ms
 
 
 class CameraLaunchSPerfClassTest(its_base_test.ItsBaseTest):
   """Test camera launch latency for S performance class as specified in CDD.
 
-  [7.5/H-1-7] MUST have camera2 startup latency (open camera to first preview
+  [7.5/H-1-6] MUST have camera2 startup latency (open camera to first preview
   frame) < 600ms as measured by the CTS camera PerformanceTest under ITS
   lighting conditions (3000K) for both primary cameras.
   """
@@ -41,7 +42,7 @@
         camera_id=self.camera_id) as cam:
 
       camera_properties_utils.skip_unless(
-          cam.is_performance_class_primary_camera())
+          cam.is_primary_camera())
 
       # Load chart for scene.
       props = cam.get_camera_properties()
@@ -55,11 +56,17 @@
         camera_id=self.camera_id)
 
     launch_ms = cam.measure_camera_launch_ms()
-    if launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD:
-      raise AssertionError(f'camera launch time: {launch_ms} ms, THRESH: '
-                           f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD} ms')
-    else:
-      logging.debug('camera launch time: %.1f ms', launch_ms)
+
+    # Assert launch time if device claims performance class
+    if (cam.is_performance_class() and
+        launch_ms >= CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD):
+      raise AssertionError(f'camera_launch_time_ms: {launch_ms}, THRESH: '
+                           f'{CAMERA_LAUNCH_S_PERFORMANCE_CLASS_THRESHOLD}')
+
+    # Log launch time, so that the corresponding MPC level can be written to
+    # report log. Text must match MPC12_CAMERA_LAUNCH_PATTERN in
+    # ItsTestActivity.java.
+    print(f'camera_launch_time_ms:{launch_ms}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
index ba4867b..0eb76eb 100644
--- a/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
+++ b/apps/CameraITS/tests/scene2_c/test_jpeg_capture_perf_class.py
@@ -22,13 +22,14 @@
 import its_base_test
 import its_session_utils
 
+# This must match MPC12_JPEG_CAPTURE_THRESHOLD in ItsTestActivity.java
 JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD = 1000  # ms
 
 
 class JpegCaptureSPerfClassTest(its_base_test.ItsBaseTest):
   """Test jpeg capture latency for S performance class as specified in CDD.
 
-  [7.5/H-1-6] MUST have camera2 JPEG capture latency < 1000ms for 1080p
+  [7.5/H-1-5] MUST have camera2 JPEG capture latency < 1000ms for 1080p
   resolution as measured by the CTS camera PerformanceTest under ITS lighting
   conditions (3000K) for both primary cameras.
   """
@@ -41,7 +42,7 @@
         camera_id=self.camera_id) as cam:
 
       camera_properties_utils.skip_unless(
-          cam.is_performance_class_primary_camera())
+          cam.is_primary_camera())
 
       # Load chart for scene.
       props = cam.get_camera_properties()
@@ -55,12 +56,18 @@
         camera_id=self.camera_id)
 
     jpeg_capture_ms = cam.measure_camera_1080p_jpeg_capture_ms()
-    if jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD:
-      raise AssertionError(f'1080p jpeg capture time: {jpeg_capture_ms} ms, '
+
+    # Assert jpeg capture time if device claims performance class
+    if (cam.is_performance_class() and
+        jpeg_capture_ms >= JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD):
+      raise AssertionError(f'1080p_jpeg_capture_time_ms: {jpeg_capture_ms}, '
                            f'THRESH: '
-                           f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD} ms')
-    else:
-      logging.debug('1080p jpeg capture time: %.1f ms', jpeg_capture_ms)
+                           f'{JPEG_CAPTURE_S_PERFORMANCE_CLASS_THRESHOLD}')
+
+    # Log jpeg capture time so that the corresponding MPC level can be written
+    # to report log. Text must match MPC12_JPEG_CAPTURE_PATTERN in
+    # ItsTestActivity.java.
+    print(f'1080p_jpeg_capture_time_ms:{jpeg_capture_ms}')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
index 9539971..c6a3bcb 100644
--- a/apps/CameraITS/tests/scene3/test_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_edge_enhancement.py
@@ -94,7 +94,6 @@
         device_id=self.dut.serial,
         camera_id=self.camera_id,
         hidden_physical_id=self.hidden_physical_id) as cam:
-      chart_loc_arg = self.chart_loc_arg
       props = cam.get_camera_properties()
       props = cam.override_with_hidden_physical_camera_props(props)
 
@@ -109,8 +108,7 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = opencv_processing_utils.Chart(
-          cam, props, self.log_path, chart_loc=chart_loc_arg)
+      chart = opencv_processing_utils.Chart(cam, props, self.log_path)
 
       # Define format
       fmt = 'yuv'
@@ -141,24 +139,26 @@
                     str(sharpness_regular))
 
       logging.debug('Verify HQ is sharper than OFF')
-      e_msg = 'HQ: %.3f, OFF: %.3f' % (sharpness_regular[EDGE_MODES['HQ']],
-                                       sharpness_regular[EDGE_MODES['OFF']])
-      assert (sharpness_regular[EDGE_MODES['HQ']] >
-              sharpness_regular[EDGE_MODES['OFF']]), e_msg
+      if (sharpness_regular[EDGE_MODES['HQ']] <=
+          sharpness_regular[EDGE_MODES['OFF']]):
+        raise AssertionError(f"HQ: {sharpness_regular[EDGE_MODES['HQ']]:.3f}, "
+                             f"OFF: {sharpness_regular[EDGE_MODES['OFF']]:.3f}")
 
       logging.debug('Verify OFF is not sharper than FAST')
-      e_msg = 'FAST: %.3f, OFF: %.3f, RTOL: %.2f' % (
-          sharpness_regular[EDGE_MODES['FAST']],
-          sharpness_regular[EDGE_MODES['OFF']], SHARPNESS_RTOL)
-      assert (sharpness_regular[EDGE_MODES['FAST']] >
-              sharpness_regular[EDGE_MODES['OFF']]*(1.0-SHARPNESS_RTOL)), e_msg
+      if (sharpness_regular[EDGE_MODES['FAST']] <=
+          sharpness_regular[EDGE_MODES['OFF']]*(1.0-SHARPNESS_RTOL)):
+        raise AssertionError(
+            f"FAST: {sharpness_regular[EDGE_MODES['FAST']]:.3f}, "
+            f"OFF: {sharpness_regular[EDGE_MODES['OFF']]:.3f}, "
+            f"RTOL: {SHARPNESS_RTOL}")
 
       logging.debug('Verify FAST is not sharper than HQ')
-      e_msg = 'HQ: %.3f, FAST: %.3f, RTOL: %.2f' % (
-          sharpness_regular[EDGE_MODES['HQ']],
-          sharpness_regular[EDGE_MODES['FAST']], SHARPNESS_RTOL)
-      assert (sharpness_regular[EDGE_MODES['HQ']] >
-              sharpness_regular[EDGE_MODES['FAST']]*(1.0-SHARPNESS_RTOL)), e_msg
+      if (sharpness_regular[EDGE_MODES['HQ']] <=
+          sharpness_regular[EDGE_MODES['FAST']]*(1.0-SHARPNESS_RTOL)):
+        raise AssertionError(
+            f"HQ: {sharpness_regular[EDGE_MODES['HQ']]:.3f}, "
+            f"FAST: {sharpness_regular[EDGE_MODES['FAST']]:.3f}, "
+            f"RTOL: {SHARPNESS_RTOL}")
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_flip_mirror.py b/apps/CameraITS/tests/scene3/test_flip_mirror.py
index 2dff574..f190477 100644
--- a/apps/CameraITS/tests/scene3/test_flip_mirror.py
+++ b/apps/CameraITS/tests/scene3/test_flip_mirror.py
@@ -137,15 +137,13 @@
       props = cam.get_camera_properties()
       props = cam.override_with_hidden_physical_camera_props(props)
       debug = self.debug_mode
-      chart_loc_arg = self.chart_loc_arg
 
       # load chart for scene
       its_session_utils.load_scene(
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # initialize chart class and locate chart in scene
-      chart = opencv_processing_utils.Chart(
-          cam, props, self.log_path, chart_loc=chart_loc_arg)
+      chart = opencv_processing_utils.Chart(cam, props, self.log_path)
       fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
 
       # test that image is not flipped, mirrored, or rotated
diff --git a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
index 3e638d6..930817c 100644
--- a/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
+++ b/apps/CameraITS/tests/scene3/test_lens_movement_reporting.py
@@ -105,7 +105,6 @@
         device_id=self.dut.serial,
         camera_id=self.camera_id,
         hidden_physical_id=self.hidden_physical_id) as cam:
-      chart_loc_arg = self.chart_loc_arg
       props = cam.get_camera_properties()
       props = cam.override_with_hidden_physical_camera_props(props)
 
@@ -120,8 +119,7 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = opencv_processing_utils.Chart(
-          cam, props, self.log_path, chart_loc=chart_loc_arg)
+      chart = opencv_processing_utils.Chart(cam, props, self.log_path)
 
       # Get proper sensitivity, exposure time, and focus distance with 3A.
       mono_camera = camera_properties_utils.mono_camera(props)
diff --git a/apps/CameraITS/tests/scene3/test_lens_position.py b/apps/CameraITS/tests/scene3/test_lens_position.py
deleted file mode 100644
index 09f74f0..0000000
--- a/apps/CameraITS/tests/scene3/test_lens_position.py
+++ /dev/null
@@ -1,232 +0,0 @@
-# Copyright 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.
-"""Verifies android.lens.focusDistance for lens moving and stationary."""
-
-
-import logging
-import os
-from mobly import test_runner
-import numpy as np
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import error_util
-import image_processing_utils
-import its_session_utils
-import opencv_processing_utils
-
-FRAME_ATOL_MS = 10  # ms
-LENS_MOVING_STATE = 1
-NAME = os.path.splitext(os.path.basename(__file__))[0]
-NSEC_TO_MSEC = 1.0E-6
-NUM_TRYS = 2
-NUM_STEPS = 6
-POSITION_RTOL = 0.1
-SHARPNESS_RTOL = 0.1
-VGA_W, VGA_H = 640, 480
-
-
-def assert_static_frames_behavior(d_stat):
-  """Assert locations/sharpness are correct in static frames."""
-  logging.debug('Asserting static lens locations/sharpness are similar')
-  for i in range(len(d_stat) // 2):
-    j = 2 * NUM_STEPS - 1 - i
-    rw_msg = 'fd_write: %.3f, fd_read: %.3f, RTOL: %.2f' % (
-        d_stat[i]['fd'], d_stat[i]['loc'], POSITION_RTOL)
-    fr_msg = 'loc_fwd[%d]: %.3f, loc_rev[%d]: %.3f, RTOL: %.2f' % (
-        i, d_stat[i]['loc'], j, d_stat[j]['loc'], POSITION_RTOL)
-    s_msg = 'sharpness_fwd: %.3f, sharpness_rev: %.3f, RTOL: %.2f' % (
-        d_stat[i]['sharpness'], d_stat[j]['sharpness'], SHARPNESS_RTOL)
-    assert np.isclose(d_stat[i]['loc'], d_stat[i]['fd'],
-                      rtol=POSITION_RTOL), rw_msg
-    assert np.isclose(d_stat[i]['loc'], d_stat[j]['loc'],
-                      rtol=POSITION_RTOL), fr_msg
-    assert np.isclose(d_stat[i]['sharpness'], d_stat[j]['sharpness'],
-                      rtol=SHARPNESS_RTOL), s_msg
-
-
-def assert_moving_frames_behavior(d_move, d_stat):
-  """Assert locations/sharpness are correct for consecutive moving frames."""
-  logging.debug('Asserting moving frames are consecutive')
-  times = [v['timestamp'] for v in d_move.values()]
-  diffs = np.gradient(times)
-  assert np.isclose(np.amin(diffs), np.amax(diffs),
-                    atol=FRAME_ATOL_MS), 'ATOL(ms): %.1f' % FRAME_ATOL_MS
-
-  logging.debug('Asserting moving lens locations/sharpness are similar')
-  for i in range(len(d_move)):
-    e_msg = 'static: %.3f, moving: %.3f, RTOL: %.2f' % (
-        d_stat[i]['loc'], d_move[i]['loc'], POSITION_RTOL)
-    assert np.isclose(d_stat[i]['loc'], d_move[i]['loc'],
-                      rtol=POSITION_RTOL), e_msg
-    if d_move[i]['lens_moving'] and i > 0:
-      e_msg = '%d sharpness[stat]: %.2f ' % (i-1, d_stat[i-1]['sharpness'])
-      e_msg += '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
-          i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
-      if d_stat[i]['sharpness'] > d_stat[i-1]['sharpness']:
-        assert (d_stat[i]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
-                d_move[i]['sharpness'] > d_stat[i-1]['sharpness'] *
-                (1.0 - SHARPNESS_RTOL)), e_msg
-      else:
-        assert (d_stat[i-1]['sharpness'] * (1.0 + SHARPNESS_RTOL) >
-                d_move[i]['sharpness'] > d_stat[i]['sharpness'] *
-                (1.0 - SHARPNESS_RTOL)), e_msg
-    elif not d_move[i]['lens_moving']:
-      e_msg = '%d sharpness[stat]: %.2f, [move]: %.2f, RTOL: %.1f' % (
-          i, d_stat[i]['sharpness'], d_move[i]['sharpness'], SHARPNESS_RTOL)
-      assert np.isclose(d_stat[i]['sharpness'], d_move[i]['sharpness'],
-                        rtol=SHARPNESS_RTOL), e_msg
-    else:
-      raise error_util.Error('Lens is moving at frame 0!')
-
-
-def take_caps_and_return_data(cam, props, fmt, sens, exp, chart, log_path):
-  """Return fd, sharpness, lens state of the output images.
-
-  Args:
-    cam: An open device session
-    props: Properties of cam
-    fmt: Dict for capture format
-    sens: Sensitivity for 3A request as defined in android.sensor.sensitivity
-    exp: Exposure time for 3A request as defined in android.sensor.exposureTime
-    chart: Object with chart properties
-    log_path: Location to save images
-
-  Returns:
-    Dictionary of results for different focal distance captures with static
-    lens positions and moving lens positions: d_static, d_moving
-  """
-
-  # initialize variables and take data sets
-  data_static = {}
-  data_moving = {}
-  white_level = int(props['android.sensor.info.whiteLevel'])
-  min_fd = props['android.lens.info.minimumFocusDistance']
-  hyperfocal = props['android.lens.info.hyperfocalDistance']
-  # create forward + back list of focal distances
-  fds_f = np.arange(hyperfocal, min_fd, (min_fd-hyperfocal)/(NUM_STEPS-1))
-  fds_f = np.append(fds_f, min_fd)
-  fds_fb = list(fds_f) + list(reversed(fds_f))
-
-  # take static data set
-  for i, fd in enumerate(fds_fb):
-    req = capture_request_utils.manual_capture_request(sens, exp)
-    req['android.lens.focusDistance'] = fd
-    cap = image_processing_utils.stationary_lens_cap(cam, req, fmt)
-    data = {'fd': fds_fb[i]}
-    data['loc'] = cap['metadata']['android.lens.focusDistance']
-    y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
-    chart.img = image_processing_utils.normalize_img(
-        image_processing_utils.get_image_patch(y, chart.xnorm, chart.ynorm,
-                                               chart.wnorm, chart.hnorm))
-    image_processing_utils.write_image(chart.img, '%s_stat_i=%d_chart.jpg' % (
-        os.path.join(log_path, NAME), i))
-    data['sharpness'] = white_level*image_processing_utils.compute_image_sharpness(
-        chart.img)
-    data_static[i] = data
-
-  # take moving data set
-  reqs = []
-  for i, fd in enumerate(fds_f):
-    reqs.append(capture_request_utils.manual_capture_request(sens, exp))
-    reqs[i]['android.lens.focusDistance'] = fd
-  caps = cam.do_capture(reqs, fmt)
-  for i, cap in enumerate(caps):
-    data = {'fd': fds_f[i]}
-    data['loc'] = cap['metadata']['android.lens.focusDistance']
-    data['lens_moving'] = (
-        cap['metadata']['android.lens.state'] == LENS_MOVING_STATE)
-    timestamp = cap['metadata']['android.sensor.timestamp'] * NSEC_TO_MSEC
-    if i == 0:
-      timestamp_init = timestamp
-    timestamp -= timestamp_init
-    data['timestamp'] = timestamp
-    y, _, _ = image_processing_utils.convert_capture_to_planes(cap, props)
-    y = image_processing_utils.rotate_img_per_argv(y)
-    chart.img = image_processing_utils.normalize_img(
-        image_processing_utils.get_image_patch(
-            y, chart.xnorm, chart.ynorm, chart.wnorm, chart.hnorm))
-    image_processing_utils.write_image(chart.img, '%s_move_i=%d_chart.jpg' % (
-        os.path.join(log_path, NAME), i))
-    data['sharpness'] = (
-        white_level * image_processing_utils.compute_image_sharpness(chart.img))
-    data_moving[i] = data
-  return data_static, data_moving
-
-
-class LensPositionReportingTest(its_base_test.ItsBaseTest):
-  """Test if focus position is properly reported for moving lenses."""
-
-  def test_lens_position_reporting(self):
-    logging.debug('Starting %s', NAME)
-    with its_session_utils.ItsSession(
-        device_id=self.dut.serial,
-        camera_id=self.camera_id,
-        hidden_physical_id=self.hidden_physical_id) as cam:
-      chart_loc_arg = self.chart_loc_arg
-      props = cam.get_camera_properties()
-      props = cam.override_with_hidden_physical_camera_props(props)
-      log_path = self.log_path
-
-      # Check skip conditions
-      camera_properties_utils.skip_unless(
-          not camera_properties_utils.fixed_focus(props) and
-          camera_properties_utils.read_3a(props) and
-          camera_properties_utils.lens_calibrated(props))
-
-      # Calculate camera_fov and load scaled image on tablet.
-      its_session_utils.load_scene(cam, props, self.scene, self.tablet,
-                                   self.chart_distance)
-
-      # Initialize chart class and locate chart in scene
-      chart = opencv_processing_utils.Chart(
-          cam, props, self.log_path, chart_loc=chart_loc_arg)
-
-      # Initialize capture format
-      fmt = {'format': 'yuv', 'width': VGA_W, 'height': VGA_H}
-
-      # Get proper sensitivity and exposure time with 3A
-      mono_camera = camera_properties_utils.mono_camera(props)
-      s, e, _, _, _ = cam.do_3a(get_results=True, mono_camera=mono_camera)
-
-      # Take caps and get sharpness for each focal distance
-      d_stat, d_move = take_caps_and_return_data(
-          cam, props, fmt, s, e, chart, log_path)
-
-      # Summarize info for log file and easier debug
-      logging.debug('Lens stationary')
-      for k in sorted(d_stat):
-        logging.debug(
-            'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
-            'sharpness: %.1f', k, d_stat[k]['fd'], d_stat[k]['loc'],
-            d_stat[k]['sharpness'])
-      logging.debug('Lens moving')
-      for k in sorted(d_move):
-        logging.debug(
-            'i: %d\tfd: %.3f\tlens location (diopters): %.3f \t'
-            'sharpness: %.1f  \tlens_moving: %r \t'
-            'timestamp: %.1fms', k, d_move[k]['fd'], d_move[k]['loc'],
-            d_move[k]['sharpness'], d_move[k]['lens_moving'],
-            d_move[k]['timestamp'])
-
-      # assert reported location/sharpness is correct in static frames
-      assert_static_frames_behavior(d_stat)
-
-      # assert reported location/sharpness is correct in moving frames
-      assert_moving_frames_behavior(d_move, d_stat)
-
-
-if __name__ == '__main__':
-  test_runner.main()
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index b19ac1f..c5e9b19 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -16,6 +16,7 @@
 
 import logging
 import os
+import math
 import matplotlib
 from matplotlib import pylab
 from mobly import test_runner
@@ -44,25 +45,25 @@
                          f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}")
 
   logging.debug('Verify ZSL is similar to OFF')
-  e_msg = 'ZSL: %.5f, OFF: %.5f, RTOL: %.2f' % (
-      sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
-      SHARPNESS_RTOL)
-  assert np.isclose(sharpness[EDGE_MODES['ZSL']], sharpness[EDGE_MODES['OFF']],
-                    SHARPNESS_RTOL), e_msg
+  if not math.isclose(sharpness[EDGE_MODES['ZSL']],
+                      sharpness[EDGE_MODES['OFF']], rel_tol=SHARPNESS_RTOL):
+    raise AssertionError(f"ZSL: {sharpness[EDGE_MODES['ZSL']]:.5f}, "
+                         f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}, "
+                         f'RTOL: {SHARPNESS_RTOL}')
 
   logging.debug('Verify OFF is not sharper than FAST')
-  e_msg = 'FAST: %.5f, OFF: %.5f, RTOL: %.2f' % (
-      sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['OFF']],
-      SHARPNESS_RTOL)
-  assert (sharpness[EDGE_MODES['FAST']] >
-          sharpness[EDGE_MODES['OFF']] * (1.0-SHARPNESS_RTOL)), e_msg
+  if (sharpness[EDGE_MODES['FAST']] <=
+      sharpness[EDGE_MODES['OFF']] * (1.0-SHARPNESS_RTOL)):
+    raise AssertionError(f"FAST: {sharpness[EDGE_MODES['FAST']]:.5f}, "
+                         f"OFF: {sharpness[EDGE_MODES['OFF']]:.5f}, "
+                         f'RTOL: {SHARPNESS_RTOL}')
 
   logging.debug('Verify FAST is not sharper than HQ')
-  e_msg = 'FAST: %.5f, HQ: %.5f, RTOL: %.2f' % (
-      sharpness[EDGE_MODES['FAST']], sharpness[EDGE_MODES['HQ']],
-      SHARPNESS_RTOL)
-  assert (sharpness[EDGE_MODES['HQ']] >
-          sharpness[EDGE_MODES['FAST']] * (1.0-SHARPNESS_RTOL)), e_msg
+  if (sharpness[EDGE_MODES['HQ']] <=
+      sharpness[EDGE_MODES['FAST']] * (1.0-SHARPNESS_RTOL)):
+    raise AssertionError(f"FAST: {sharpness[EDGE_MODES['FAST']]:.5f}, "
+                         f"HQ: {sharpness[EDGE_MODES['HQ']]:.5f}, "
+                         f'RTOL: {SHARPNESS_RTOL}')
 
 
 def do_capture_and_determine_sharpness(
@@ -139,7 +140,6 @@
         device_id=self.dut.serial,
         camera_id=self.camera_id,
         hidden_physical_id=self.hidden_physical_id) as cam:
-      chart_loc_arg = self.chart_loc_arg
       props = cam.get_camera_properties()
       props = cam.override_with_hidden_physical_camera_props(props)
       log_path = self.log_path
@@ -157,12 +157,11 @@
           cam, props, self.scene, self.tablet, self.chart_distance)
 
       # Initialize chart class and locate chart in scene
-      chart = opencv_processing_utils.Chart(
-          cam, props, self.log_path, chart_loc=chart_loc_arg)
+      chart = opencv_processing_utils.Chart(cam, props, self.log_path)
 
       # If reprocessing is supported, ZSL edge mode must be avaiable.
-      assert camera_properties_utils.edge_mode(
-          props, EDGE_MODES['ZSL']), 'ZSL android.edge.mode not available!'
+      if not camera_properties_utils.edge_mode(props, EDGE_MODES['ZSL']):
+        raise AssertionError('ZSL android.edge.mode not available!')
 
       reprocess_formats = []
       if camera_properties_utils.yuv_reprocess(props):
@@ -251,11 +250,13 @@
         hq_div_off_regular = (
             sharpness_regular[EDGE_MODES['HQ']] /
             sharpness_regular[EDGE_MODES['OFF']])
-        e_msg = 'HQ/OFF_reprocess: %.4f, HQ/OFF_reg: %.4f, RTOL: %.2f' % (
-            hq_div_off_reprocess, hq_div_off_regular, SHARPNESS_RTOL)
         logging.debug('Verify reprocess HQ ~= reg HQ relative to OFF')
-        assert np.isclose(hq_div_off_reprocess, hq_div_off_regular,
-                          SHARPNESS_RTOL), e_msg
+        if not math.isclose(hq_div_off_reprocess, hq_div_off_regular,
+                            rel_tol=SHARPNESS_RTOL):
+          raise AssertionError(f'HQ/OFF_reprocess: {hq_div_off_reprocess:.4f}, '
+                               f'HQ/OFF_reg: {hq_div_off_regular:.4f}, '
+                               f'RTOL: {SHARPNESS_RTOL}')
+
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index 73d1276..4ed835b 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -126,9 +126,9 @@
     else:
       logging.debug('Skipping camera. Not appropriate for test rig.')
 
-  e_msg = 'Error: started with 2+ cameras, reduced to <2. Wrong test rig?'
-  e_msg += '\ntest_ids: %s' % str(test_ids)
-  assert len(test_ids) >= 2, e_msg
+  if len(test_ids) < 2:
+    raise AssertionError('Error: started with 2+ cameras, reduced to <2 based '
+                         f'on FoVs. Wrong test rig? test_ids: {test_ids}')
   return test_ids[0:2]
 
 
@@ -562,9 +562,10 @@
       err_mm = np.linalg.norm(np.array([x_w[i_ref], y_w[i_ref]]) -
                               np.array([x_w[i_2nd], y_w[i_2nd]])) * M_TO_MM
       logging.debug('Center location err (mm): %.2f', err_mm)
-      msg = 'Center locations %s <-> %s too different!' % (i_ref, i_2nd)
-      msg += ' val=%.2f, ATOL=%.f mm' % (err_mm, ALIGN_TOL_MM)
-      assert err_mm < ALIGN_TOL_MM, msg
+      if err_mm > ALIGN_TOL_MM:
+        raise AssertionError(
+            f'Centers {i_ref} <-> {i_2nd} too different! '
+            f'val={err_mm:.2f}, ATOL={ALIGN_TOL_MM} mm')
 
       # Check projections back into pixel space
       for i in [i_ref, i_2nd]:
@@ -572,9 +573,9 @@
                              np.array([x_p[i], y_p[i]]).reshape(1, -1))
         logging.debug('Camera %s projection error (pixels): %.1f', i, err)
         tol = ALIGN_TOL * sensor_diag[i]
-        msg = 'Camera %s project location too different!' % i
-        msg += ' diff=%.2f, ATOL=%.2f pixels' % (err, tol)
-        assert err < tol, msg
+        if err >= tol:
+          raise AssertionError(f'Camera {i} project location too different! '
+                               f'diff={err:.2f}, ATOL={tol:.2f} pixels')
 
       # Check focal length and circle size if more than 1 focal length
       if len(fl) > 1:
@@ -584,11 +585,12 @@
                       fl[i_ref], fl[i_2nd])
         logging.debug('Pixel size (um); ref: %.2f, 2nd: %.2f',
                       pixel_sizes[i_ref], pixel_sizes[i_2nd])
-        msg = 'Circle size scales improperly! RTOL=%.1f\n' % CIRCLE_RTOL
-        msg += 'Metric: radius/focal_length*sensor_diag should be equal.'
-        assert np.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
-                          circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
-                          rtol=CIRCLE_RTOL), msg
+        if not math.isclose(circle[i_ref]['r']*pixel_sizes[i_ref]/fl[i_ref],
+                            circle[i_2nd]['r']*pixel_sizes[i_2nd]/fl[i_2nd],
+                            rel_tol=CIRCLE_RTOL):
+          raise AssertionError(
+              f'Circle size scales improperly! RTOL: {CIRCLE_RTOL} '
+              'Metric: radius*pixel_size/focal_length should be equal.')
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene6/test_zoom.py b/apps/CameraITS/tests/scene6/test_zoom.py
index 4457046..89cdae5 100644
--- a/apps/CameraITS/tests/scene6/test_zoom.py
+++ b/apps/CameraITS/tests/scene6/test_zoom.py
@@ -39,6 +39,7 @@
 NAME = os.path.splitext(os.path.basename(__file__))[0]
 NUM_STEPS = 10
 OFFSET_RTOL = 0.15
+OFFSET_RTOL_MIN_FD = 0.30
 RADIUS_RTOL = 0.10
 RADIUS_RTOL_MIN_FD = 0.15
 ZOOM_MAX_THRESH = 10.0
@@ -88,9 +89,9 @@
     # determine if minimum focus distance is less than rig depth
     if (math.isclose(min_fd, 0.0, rel_tol=1E-6) or  # fixed focus
         1.0/min_fd < chart_distance_m*MIN_FOCUS_DIST_TOL):
-      test_tols[focal_l] = RADIUS_RTOL
+      test_tols[focal_l] = (RADIUS_RTOL, OFFSET_RTOL)
     else:
-      test_tols[focal_l] = RADIUS_RTOL_MIN_FD
+      test_tols[focal_l] = (RADIUS_RTOL_MIN_FD, OFFSET_RTOL_MIN_FD)
       logging.debug('loosening RTOL for cam[%s]: '
                     'min focus distance too large.', i)
   # find intersection of formats for max common format
@@ -233,7 +234,7 @@
             cam, props, self.chart_distance, debug)
       else:
         fl = props['android.lens.info.availableFocalLengths'][0]
-        test_tols = {fl: RADIUS_RTOL}
+        test_tols = {fl: (RADIUS_RTOL, OFFSET_RTOL)}
         yuv_size = capture_request_utils.get_largest_yuv_format(props)
         size = [yuv_size['width'], yuv_size['height']]
       logging.debug('capture size: %s', str(size))
@@ -255,22 +256,30 @@
 
         # determine radius tolerance of capture
         cap_fl = cap['metadata']['android.lens.focalLength']
-        radius_tol = test_tols[cap_fl]
+        radius_tol, offset_tol = test_tols[cap_fl]
 
         # convert to [0, 255] images with unsigned integer
         img *= 255
         img = img.astype(np.uint8)
 
         # Find the center circle in img
-        circle = find_center_circle(
-            img, img_name, CIRCLE_COLOR,
-            min_area=MIN_AREA_RATIO * size[0] * size[1] * z * z,
-            debug=debug)
-        if circle_cropped(circle, size):
-          logging.debug('zoom %.2f is too large! Skip further captures', z)
-          break
+        try:
+          circle = find_center_circle(
+              img, img_name, CIRCLE_COLOR,
+              min_area=MIN_AREA_RATIO * size[0] * size[1] * z * z,
+              debug=debug)
+          if circle_cropped(circle, size):
+            logging.debug('zoom %.2f is too large! Skip further captures', z)
+            break
+        except AssertionError:
+          if z/z_list[0] >= ZOOM_MAX_THRESH:
+            break
+          else:
+            raise AssertionError(
+                f'No circle was detected for zoom ratio <= {ZOOM_MAX_THRESH}. '
+                'Please take pictures according to instructions carefully!')
         test_data[i] = {'z': z, 'circle': circle, 'r_tol': radius_tol,
-                        'fl': cap_fl}
+                        'o_tol': offset_tol, 'fl': cap_fl}
 
     # assert some range is tested before circles get too big
     zoom_max_thresh = ZOOM_MAX_THRESH
@@ -307,16 +316,16 @@
       # check relative offset against init vals w/ no focal length change
       if i == 0 or test_data[i-1]['fl'] != data['fl']:  # set init values
         z_init = float(data['z'])
-        offset_init = [data['circle'][0] - size[0]//2,
-                       data['circle'][1] - size[1]//2]
+        offset_init = [(data['circle'][0] - size[0] // 2),
+                       (data['circle'][1] - size[1] // 2)]
       else:  # check
         z_ratio = data['z'] / z_init
         offset_rel = (distance(offset_abs[0], offset_abs[1]) / z_ratio /
                       distance(offset_init[0], offset_init[1]))
         logging.debug('offset_rel: %.3f', offset_rel)
-        if not math.isclose(offset_rel, 1.0, rel_tol=OFFSET_RTOL):
-          raise AssertionError(f"zoom: {data['z']:.2f}, offset(rel): "
-                               f'{offset_rel:.4f}, RTOL: {OFFSET_RTOL}')
+        if not math.isclose(offset_rel, 1.0, rel_tol=data['o_tol']):
+          raise AssertionError(f"zoom: {data['z']:.2f}, offset(rel to 1): "
+                               f"{offset_rel:.4f}, RTOL: {data['o_tol']}")
 
 if __name__ == '__main__':
   test_runner.main()
diff --git a/apps/CameraITS/tests/scene_change/scene_change.png b/apps/CameraITS/tests/scene_change/scene_change.png
deleted file mode 100644
index b9554b3..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png b/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png
deleted file mode 100644
index 92e3444..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change_0.5x_scaled.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png b/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png
deleted file mode 100644
index d8cca5c..0000000
--- a/apps/CameraITS/tests/scene_change/scene_change_0.67x_scaled.png
+++ /dev/null
Binary files differ
diff --git a/apps/CameraITS/tests/scene_change/test_scene_change.py b/apps/CameraITS/tests/scene_change/test_scene_change.py
deleted file mode 100644
index 9a8b262..0000000
--- a/apps/CameraITS/tests/scene_change/test_scene_change.py
+++ /dev/null
@@ -1,243 +0,0 @@
-# Copyright 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Verify that the android.control.afSceneChange asserted on scene change."""
-
-
-import logging
-import multiprocessing
-import os.path
-import time
-
-from mobly import test_runner
-
-import its_base_test
-import camera_properties_utils
-import capture_request_utils
-import image_processing_utils
-import its_session_utils
-import scene_change_utils
-
-_BRIGHT_CHANGE_TOL = 0.2
-_CONTINUOUS_PICTURE_MODE = 4
-_CONVERGED_3A = (2, 2, 2)  # (AE, AF, AWB)
-_DELAY_CAPTURE = 1.5  # Delay in first capture to sync events (sec).
-_DELAY_DISPLAY = 3.0  # Time when display turns OFF (sec).
-_FPS = 30  # Frames Per Second
-_M_TO_CM = 100
-_NAME = os.path.splitext(os.path.basename(__file__))[0]
-_NSEC_TO_MSEC = 1E-6
-_NUM_TRIES = 6
-_NUM_FRAMES = 50
-_PATCH_H = 0.1  # Center 10%.
-_PATCH_W = 0.1
-_PATCH_X = 0.5 - _PATCH_W/2
-_PATCH_Y = 0.5 - _PATCH_H/2
-_RGB_G_CH = 1
-_SCENE_CHANGE_FLAG_TRUE = 1
-_VALID_SCENE_CHANGE_VALS = (0, 1)
-_VGA_W, _VGA_H = 640, 480
-
-
-def find_3a_converged_frame(cap_data):
-  converged_frame = -1
-  for i, cap in enumerate(cap_data):
-    if cap['3a_state'] == _CONVERGED_3A:
-      converged_frame = i
-      break
-  logging.debug('Frame index where 3A converges: %d', converged_frame)
-  return converged_frame
-
-
-def determine_if_scene_changed(cap_data, converged_frame):
-  """Determine if the scene has changed during captures.
-
-  Args:
-    cap_data: Camera capture object.
-    converged_frame: Integer indicating when 3A converged.
-
-  Returns:
-    A 2-tuple of booleans where the first is for AF scene change flag asserted
-    and the second is for whether brightness in images changed.
-  """
-  scene_change_flag = False
-  bright_change_flag = False
-  start_frame_brightness = cap_data[0]['avg']
-  for i in range(converged_frame, len(cap_data)):
-    if cap_data[i]['avg'] <= (
-        start_frame_brightness * (1.0 - _BRIGHT_CHANGE_TOL)):
-      bright_change_flag = True
-    if cap_data[i]['flag'] == _SCENE_CHANGE_FLAG_TRUE:
-      scene_change_flag = True
-  return scene_change_flag, bright_change_flag
-
-
-def toggle_screen(tablet, delay=1):
-  """Sets the chart host screen display level .
-
-  Args:
-    tablet: Object for screen tablet.
-    delay: Float value for time delay. Default is 1 second.
-  """
-  t0 = time.time()
-  if delay >= 0:
-    time.sleep(delay)
-  else:
-    raise ValueError(f'Screen toggle time shifted to {delay} w/o scene change. '
-                     'Tablet does not appear to be toggling. Check setup.')
-  tablet.adb.shell('input keyevent KEYCODE_POWER')
-  t = time.time() - t0
-  logging.debug('Toggling display at %.3f.', t)
-
-
-def capture_frames(cam, delay, burst, log_path):
-  """Capture NUM_FRAMES frames and log metadata.
-
-  3A state information:
-    AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
-                4: FLASH_REQ, 5: PRECAPTURE}
-    AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
-                3: ACTIVE_SCAN, 4: FOCUS_LOCKED, 5: NOT_FOCUSED_LOCKED,
-                6: PASSIVE_UNFOCUSED}
-    AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
-
-  Args:
-    cam: Camera object.
-    delay: Float value for time delay in seconds.
-    burst: Integer number of burst index.
-    log_path: String location to save images.
-  Returns:
-    cap_data_list. List of dicts for each capture.
-  """
-  cap_data_list = []
-  req = capture_request_utils.auto_capture_request()
-  req['android.control.afMode'] = _CONTINUOUS_PICTURE_MODE
-  fmt = {'format': 'yuv', 'width': _VGA_W, 'height': _VGA_H}
-  t0 = time.time()
-  time.sleep(delay)
-  logging.debug('cap event start: %.6f', time.time() - t0)
-  caps = cam.do_capture([req]*_NUM_FRAMES, fmt)
-  logging.debug('cap event stop: %.6f', time.time() - t0)
-
-  # Extract frame metadata.
-  for i, cap in enumerate(caps):
-    cap_data = {}
-    exp = cap['metadata']['android.sensor.exposureTime'] * _NSEC_TO_MSEC
-    iso = cap['metadata']['android.sensor.sensitivity']
-    focal_length = cap['metadata']['android.lens.focalLength']
-    ae_state = cap['metadata']['android.control.aeState']
-    af_state = cap['metadata']['android.control.afState']
-    awb_state = cap['metadata']['android.control.awbState']
-    if focal_length:
-      fl_str = str(round(_M_TO_CM/focal_length, 2)) + 'cm'
-    else:
-      fl_str = 'infinity'
-    flag = cap['metadata']['android.control.afSceneChange']
-    if flag not in _VALID_SCENE_CHANGE_VALS:
-      raise AssertionError(f'afSceneChange not a valid value: {flag}.')
-    img = image_processing_utils.convert_capture_to_rgb_image(cap)
-    image_processing_utils.write_image(
-        img, '%s_%d_%d.jpg' % (os.path.join(log_path, _NAME), burst, i))
-    patch = image_processing_utils.get_image_patch(
-        img, _PATCH_X, _PATCH_Y, _PATCH_W, _PATCH_H)
-    green_avg = image_processing_utils.compute_image_means(patch)[_RGB_G_CH]
-    logging.debug(
-        '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f, 3A: [%d,%d,%d], flag: %d',
-        i, iso, exp, fl_str, green_avg, ae_state, af_state, awb_state, flag)
-    cap_data['3a_state'] = (ae_state, af_state, awb_state)
-    cap_data['avg'] = green_avg
-    cap_data['flag'] = flag
-    cap_data_list.append(cap_data)
-  return cap_data_list
-
-
-class SceneChangeTest(its_base_test.ItsBaseTest):
-  """Tests that AF scene change detected metadata changes for scene change.
-
-  Confirm android.control.afSceneChangeDetected is asserted when scene changes.
-
-  Does continuous capture with face scene during scene change. With no scene
-  change, behavior should be similar to scene2_b/test_continuous_picture.
-  Scene change is modeled with scene tablet powered down during continuous
-  capture. If tablet does not exist, scene change can be modeled with hand wave
-  in front of camera.
-
-  Depending on scene brightness changes and scene change flag assertions during
-  test, adjust tablet timing to move scene change to appropriate timing for
-  test.
-  """
-
-  def test_scene_change(self):
-    logging.debug('Starting %s', _NAME)
-    with its_session_utils.ItsSession(
-        device_id=self.dut.serial,
-        camera_id=self.camera_id,
-        hidden_physical_id=self.hidden_physical_id) as cam:
-      props = cam.get_camera_properties()
-      props = cam.override_with_hidden_physical_camera_props(props)
-      log_path = self.log_path
-      tablet = self.tablet
-
-      # Check SKIP conditions.
-      camera_properties_utils.skip_unless(
-          camera_properties_utils.continuous_picture(props) and
-          camera_properties_utils.af_scene_change(props))
-
-      # Load chart for scene.
-      its_session_utils.load_scene(
-          cam, props, self.scene, tablet, self.chart_distance)
-
-      # Do captures with scene change.
-      tablet_level = int(self.tablet_screen_brightness)
-      logging.debug('Tablet brightness: %d', tablet_level)
-      scene_change_delay = _DELAY_DISPLAY
-      cam.do_3a()  # Do 3A up front to settle camera.
-      for burst in range(_NUM_TRIES):
-        logging.debug('burst number: %d', burst)
-        # Create scene change by turning off chart display & capture frames
-        if tablet:
-          multiprocessing.Process(name='p1', target=toggle_screen,
-                                  args=(tablet, scene_change_delay,)).start()
-        else:
-          print('Wave hand in front of camera to create scene change.')
-        cap_data = capture_frames(cam, _DELAY_CAPTURE, burst, log_path)
-
-        # Find frame where 3A converges and final brightness.
-        converged_frame = find_3a_converged_frame(cap_data)
-        converged_flag = True if converged_frame != -1 else False
-        bright_final = cap_data[_NUM_FRAMES - 1]['avg']
-
-        # Determine if scene changed.
-        scene_change_flag, bright_change_flag = determine_if_scene_changed(
-            cap_data, converged_frame)
-
-        # Adjust timing based on captured frames and scene change flags.
-        timing_adjustment = scene_change_utils.calc_timing_adjustment(
-            converged_flag, scene_change_flag, bright_change_flag, bright_final)
-        if timing_adjustment == scene_change_utils.SCENE_CHANGE_PASS_CODE:
-          break
-        elif timing_adjustment == scene_change_utils.SCENE_CHANGE_FAIL_CODE:
-          raise AssertionError('Test fails. Check logging.error.')
-        else:
-          if burst == _NUM_TRIES-1:  # FAIL out after NUM_TRIES.
-            raise AssertionError(f'No scene change in {_NUM_TRIES}x tries.')
-          else:
-            scene_change_delay += timing_adjustment / _FPS
-
-        if tablet:
-          logging.debug('Turning screen back ON.')
-          toggle_screen(tablet)
-
-
-if __name__ == '__main__':
-  test_runner.main()
diff --git a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
index 4d5a36f..0b2bbcc 100644
--- a/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -340,7 +340,7 @@
             gframe0, mask=pre_mask, **_CV2_FEATURE_PARAMS_PREMASK)
       num_features = len(p0_filtered)
       if num_features < _FEATURE_PTS_MIN:
-        for pt in p0_filtered:
+        for pt in np.rint(p0_filtered).astype(int):
           x, y = pt[0][0], pt[0][1]
           cv2.circle(frames[j], (x, y), 3, (100, 255, 255), -1)
         image_processing_utils.write_image(
@@ -369,7 +369,7 @@
         # Save debug visualization of features that are being
         # tracked in the first frame.
         frame = frames[j]
-        for x, y in p0_filtered[st == 1]:
+        for x, y in np.rint(p0_filtered[st == 1]).astype(int):
           cv2.circle(frame, (x, y), 3, (100, 255, 255), -1)
         image_processing_utils.write_image(
             frame, f'{file_name_stem}_features{j+_START_FRAME:03d}.png')
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index d5d3b06..c285a69 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -16,6 +16,7 @@
 import logging
 import os
 import os.path
+import re
 import subprocess
 import sys
 import tempfile
@@ -41,6 +42,7 @@
 RESULT_FAIL = 'FAIL'
 RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
 RESULT_KEY = 'result'
+METRICS_KEY = 'mpc_metrics'
 SUMMARY_KEY = 'summary'
 RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
 ITS_TEST_ACTIVITY = 'com.android.cts.verifier/.camera.its.ItsTestActivity'
@@ -61,13 +63,13 @@
 _ALL_SCENES = [
     'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
     'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene5', 'scene6',
-    'sensor_fusion', 'scene_change'
+    'sensor_fusion'
 ]
 
 # Scenes that can be automated through tablet display
 _AUTO_SCENES = [
     'scene0', 'scene1_1', 'scene1_2', 'scene2_a', 'scene2_b', 'scene2_c',
-    'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene6', 'scene_change'
+    'scene2_d', 'scene2_e', 'scene3', 'scene4', 'scene6'
 ]
 
 # Scenes that are logically grouped and can be called as group
@@ -101,7 +103,6 @@
                      'See tests/sensor_fusion/SensorFusion.pdf for detailed '
                      'instructions.\nNote that this test will be skipped '
                      'on devices not supporting REALTIME camera timestamp.',
-    'scene_change': 'The picture with 3 faces in tests/scene2_e/scene2_e.png',
 }
 
 
@@ -127,7 +128,6 @@
         'test_yuv_plus_raw',
     ],
     'scene2_a': [
-        'test_faces',
         'test_num_faces',
     ],
     'scene4': [
@@ -251,7 +251,7 @@
     config_file_contents: a dict read from config.yml
   """
   with open(CONFIG_FILE) as file:
-    config_file_contents = yaml.load(file, yaml.FullLoader)
+    config_file_contents = yaml.safe_load(file)
   return config_file_contents
 
 
@@ -290,9 +290,9 @@
       for device_dict in android_device_contents.get('AndroidDevice'):
         for _, label in device_dict.items():
           if label == 'tablet':
-            tablet_device_id = device_dict.get('serial')
+            tablet_device_id = str(device_dict.get('serial'))
           if label == 'dut':
-            dut_device_id = device_dict.get('serial')
+            dut_device_id = str(device_dict.get('serial'))
   if device == 'tablet':
     return tablet_device_id
   else:
@@ -452,6 +452,7 @@
     for s in per_camera_scenes:
       test_params_content['scene'] = s
       results[s]['TEST_STATUS'] = []
+      results[s][METRICS_KEY] = []
 
       # unit is millisecond for execution time record in CtsVerifier
       scene_start_time = int(round(time.time() * 1000))
@@ -527,14 +528,28 @@
             test_failed = False
             test_skipped = False
             test_not_yet_mandated = False
-            line = file.read()
-            if 'Test skipped' in line:
+            test_mpc_req = ""
+            content = file.read()
+
+            # Find media performance class logging
+            lines = content.splitlines()
+            for one_line in lines:
+              # regular expression pattern must match
+              # MPC12_CAMERA_LAUNCH_PATTERN or MPC12_JPEG_CAPTURE_PATTERN in
+              # ItsTestActivity.java.
+              mpc_string_match = re.search(
+                  '^(1080p_jpeg_capture_time_ms:|camera_launch_time_ms:)', one_line)
+              if mpc_string_match:
+                test_mpc_req = one_line
+                break
+
+            if 'Test skipped' in content:
               return_string = 'SKIP '
               num_skip += 1
               test_skipped = True
               break
 
-            if 'Not yet mandated test' in line:
+            if 'Not yet mandated test' in content:
               return_string = 'FAIL*'
               num_not_mandated_fail += 1
               test_not_yet_mandated = True
@@ -547,7 +562,7 @@
 
             if test_code == 1 and not test_not_yet_mandated:
               return_string = 'FAIL '
-              if 'Problem with socket' in line and num_try != NUM_TRIES-1:
+              if 'Problem with socket' in content and num_try != NUM_TRIES-1:
                 logging.info('Retry %s/%s', s, test)
               else:
                 num_fail += 1
@@ -557,6 +572,8 @@
         logging.info('%s %s/%s', return_string, s, test)
         test_name = test.split('/')[-1].split('.')[0]
         results[s]['TEST_STATUS'].append({'test':test_name,'status':return_string.strip()})
+        if test_mpc_req:
+          results[s][METRICS_KEY].append(test_mpc_req)
         msg_short = '%s %s' % (return_string, test)
         scene_test_summary += msg_short + '\n'
 
diff --git a/apps/CameraITS/utils/image_processing_utils.py b/apps/CameraITS/utils/image_processing_utils.py
index a364b53..4f87ae8 100644
--- a/apps/CameraITS/utils/image_processing_utils.py
+++ b/apps/CameraITS/utils/image_processing_utils.py
@@ -45,18 +45,18 @@
 TEST_IMG_DIR = os.path.join(os.environ['CAMERA_ITS_TOP'], 'test_images')
 
 
-# pylint: disable=unused-argument
+def assert_props_is_not_none(props):
+  if not props:
+    raise AssertionError('props is None')
+
+
 def convert_capture_to_rgb_image(cap,
-                                 ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
-                                 yuv_off=DEFAULT_YUV_OFFSETS,
                                  props=None,
                                  apply_ccm_raw_to_rgb=True):
   """Convert a captured image object to a RGB image.
 
   Args:
      cap: A capture object as returned by its_session_utils.do_capture.
-     ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
-     yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
      props: (Optional) camera properties object (of static values);
             required for processing raw images.
      apply_ccm_raw_to_rgb: (Optional) boolean to apply color correction matrix.
@@ -67,11 +67,11 @@
   w = cap['width']
   h = cap['height']
   if cap['format'] == 'raw10':
-    assert props is not None
+    assert_props_is_not_none(props)
     cap = unpack_raw10_capture(cap)
 
   if cap['format'] == 'raw12':
-    assert props is not None
+    assert_props_is_not_none(props)
     cap = unpack_raw12_capture(cap)
 
   if cap['format'] == 'yuv':
@@ -82,7 +82,7 @@
   elif cap['format'] == 'jpeg':
     return decompress_jpeg_to_rgb_image(cap['data'])
   elif cap['format'] == 'raw' or cap['format'] == 'rawStats':
-    assert props is not None
+    assert_props_is_not_none(props)
     r, gr, gb, b = convert_capture_to_planes(cap, props)
     return convert_raw_to_rgb_image(
         r, gr, gb, b, props, cap['metadata'], apply_ccm_raw_to_rgb)
@@ -276,10 +276,10 @@
   w = cap['width']
   h = cap['height']
   if cap['format'] == 'raw10':
-    assert props is not None
+    assert_props_is_not_none(props)
     cap = unpack_raw10_capture(cap)
   if cap['format'] == 'raw12':
-    assert props is not None
+    assert_props_is_not_none(props)
     cap = unpack_raw12_capture(cap)
   if cap['format'] == 'yuv':
     y = cap['data'][0:w * h]
@@ -293,7 +293,7 @@
     return (rgb[::3].reshape(h, w, 1), rgb[1::3].reshape(h, w, 1),
             rgb[2::3].reshape(h, w, 1))
   elif cap['format'] == 'raw':
-    assert props is not None
+    assert_props_is_not_none(props)
     white_level = float(props['android.sensor.info.whiteLevel'])
     img = numpy.ndarray(
         shape=(h * w,), dtype='<u2', buffer=cap['data'][0:w * h * 2])
@@ -314,10 +314,14 @@
           'right'] - xcrop
       hcrop = props['android.sensor.info.preCorrectionActiveArraySize'][
           'bottom'] - ycrop
-      assert wfull >= wcrop >= 0
-      assert hfull >= hcrop >= 0
-      assert wfull - wcrop >= xcrop >= 0
-      assert hfull - hcrop >= ycrop >= 0
+      if not wfull >= wcrop >= 0:
+        raise AssertionError(f'wcrop: {wcrop} not in wfull: {wfull}')
+      if not  hfull >= hcrop >= 0:
+        raise AssertionError(f'hcrop: {hcrop} not in hfull: {hfull}')
+      if not wfull - wcrop >= xcrop >= 0:
+        raise AssertionError(f'xcrop: {xcrop} not in wfull-crop: {wfull-wcrop}')
+      if not hfull - hcrop >= ycrop >= 0:
+        raise AssertionError(f'ycrop: {ycrop} not in hfull-crop: {hfull-hcrop}')
       if w == wfull and h == hfull:
         # Crop needed; extract the center region.
         img = img[ycrop:ycrop + hcrop, xcrop:xcrop + wcrop]
@@ -339,7 +343,7 @@
     idxs = get_canonical_cfa_order(props)
     return [imgs[i] for i in idxs]
   elif cap['format'] == 'rawStats':
-    assert props is not None
+    assert_props_is_not_none(props)
     white_level = float(props['android.sensor.info.whiteLevel'])
     # pylint: disable=unused-variable
     mean_image, var_image = unpack_rawstats_capture(cap)
@@ -405,7 +409,7 @@
    RGB float-3 image array, with pixel values in [0.0, 1.0]
   """
     # Values required for the RAW to RGB conversion.
-  assert props is not None
+  assert_props_is_not_none(props)
   white_level = float(props['android.sensor.info.whiteLevel'])
   black_levels = props['android.sensor.blackLevelPattern']
   gains = cap_res['android.colorCorrection.gains']
@@ -613,7 +617,8 @@
     Tuple (mean_image var_image) of float-4 images, with non-normalized
     pixel values computed from the RAW16 images on the device
   """
-  assert cap['format'] == 'rawStats'
+  if cap['format'] != 'rawStats':
+    raise AssertionError(f"Unpack fmt != rawStats: {cap['format']}")
   w = cap['width']
   h = cap['height']
   img = numpy.ndarray(shape=(2 * h * w * 4,), dtype='<f', buffer=cap['data'])
@@ -691,7 +696,8 @@
     Larger value means the image is sharper.
   """
   chans = img.shape[2]
-  assert chans == 1 or chans == 3
+  if chans != 1 and chans != 3:
+    raise AssertionError(f'Not RGB or MONO image! depth: {chans}')
   if chans == 1:
     luma = img[:, :, 0]
   else:
@@ -741,7 +747,9 @@
   Returns:
     2-D grayscale image
   """
-  assert img.shape[2] == 3, 'Not an RGB image'
+  chans = img.shape[2]
+  if chans != 3:
+    raise AssertionError(f'Not an RGB image! Depth: {chans}')
   return 0.299*img[:, :, 0] + 0.587*img[:, :, 1] + 0.114*img[:, :, 2]
 
 
@@ -770,22 +778,6 @@
   return img_out
 
 
-def chart_located_per_argv(chart_loc_arg):
-  """Determine if chart already located outside of test.
-
-  If chart info provided, return location and size. If not, return None.
-  Args:
-   chart_loc_arg: chart_loc arg value.
-
-  Returns:
-    chart_loc:  float converted xnorm,ynorm,wnorm,hnorm,scale from argv
-    text.argv is of form 'chart_loc=0.45,0.45,0.1,0.1,1.0'
-  """
-  if chart_loc_arg:
-    return map(float, chart_loc_arg)
-  return None, None, None, None, None
-
-
 def stationary_lens_cap(cam, req, fmt):
   """Take up to NUM_TRYS caps and save the 1st one with lens stationary.
 
@@ -823,7 +815,10 @@
     rms_diff
   """
   len_rgb_x = len(rgb_x)
-  assert len(rgb_y) == len_rgb_x, 'The images have different number of planes.'
+  len_rgb_y = len(rgb_y)
+  if len_rgb_y != len_rgb_x:
+    raise AssertionError('RGB images have different number of planes! '
+                         f'x: {len_rgb_x}, y: {len_rgb_y}')
   return math.sqrt(sum([pow(rgb_x[i] - rgb_y[i], 2.0)
                         for i in range(len_rgb_x)]) / len_rgb_x)
 
diff --git a/apps/CameraITS/utils/its_session_utils.py b/apps/CameraITS/utils/its_session_utils.py
index 4c47388..67f0626 100644
--- a/apps/CameraITS/utils/its_session_utils.py
+++ b/apps/CameraITS/utils/its_session_utils.py
@@ -876,6 +876,7 @@
             lock_awb=False,
             get_results=False,
             ev_comp=0,
+            auto_flash=False,
             mono_camera=False):
     """Perform a 3A operation on the device.
 
@@ -896,6 +897,7 @@
       lock_awb: Request AWB lock after convergence, and wait for it.
       get_results: Return the 3A results from this function.
       ev_comp: An EV compensation value to use when running AE.
+      auto_flash: AE control boolean to enable auto flash.
       mono_camera: Boolean for monochrome camera.
 
       Region format in args:
@@ -930,6 +932,8 @@
       cmd['awbLock'] = True
     if ev_comp != 0:
       cmd['evComp'] = ev_comp
+    if auto_flash:
+      cmd['autoFlash'] = True
     if self._hidden_physical_id:
       cmd['physicalId'] = self._hidden_physical_id
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
@@ -1093,8 +1097,8 @@
                                       ' support')
     return data['strValue'] == 'true'
 
-  def is_performance_class_primary_camera(self):
-    """Query whether the camera device is an R or S performance class primary camera.
+  def is_primary_camera(self):
+    """Query whether the camera device is a primary rear/front camera.
 
     A primary rear/front facing camera is a camera device with the lowest
     camera Id for that facing.
@@ -1103,14 +1107,28 @@
       Boolean
     """
     cmd = {}
-    cmd['cmdName'] = 'isPerformanceClassPrimaryCamera'
+    cmd['cmdName'] = 'isPrimaryCamera'
     cmd['cameraId'] = self._camera_id
     self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
 
     data, _ = self.__read_response_from_socket()
-    if data['tag'] != 'performanceClassPrimaryCamera':
-      raise error_util.CameraItsError('Failed to query performance class '
-                                      'primary camera')
+    if data['tag'] != 'primaryCamera':
+      raise error_util.CameraItsError('Failed to query primary camera')
+    return data['strValue'] == 'true'
+
+  def is_performance_class(self):
+    """Query whether the mobile device is an R or S performance class device.
+
+    Returns:
+      Boolean
+    """
+    cmd = {}
+    cmd['cmdName'] = 'isPerformanceClass'
+    self.sock.send(json.dumps(cmd).encode() + '\n'.encode())
+
+    data, _ = self.__read_response_from_socket()
+    if data['tag'] != 'performanceClass':
+      raise error_util.CameraItsError('Failed to query performance class')
     return data['strValue'] == 'true'
 
   def measure_camera_launch_ms(self):
diff --git a/apps/CameraITS/utils/lighting_control_utils.py b/apps/CameraITS/utils/lighting_control_utils.py
new file mode 100644
index 0000000..a78c658
--- /dev/null
+++ b/apps/CameraITS/utils/lighting_control_utils.py
@@ -0,0 +1,100 @@
+# 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.
+"""Utility functions for sensor_fusion hardware rig."""
+
+
+import logging
+import struct
+import time
+import sensor_fusion_utils
+
+# Constants for Arduino
+ARDUINO_BRIGHTNESS_MAX = 255
+ARDUINO_BRIGHTNESS_MIN = 0
+ARDUINO_LIGHT_START_BYTE = 254
+
+
+def set_light_brightness(ch, brightness, serial_port, delay=0):
+  """Turn on light to specified brightness.
+
+  Args:
+    ch: str; light to turn on in ARDUINO_VALID_CH
+    brightness: int value of brightness between 0 and 255.
+    serial_port: object; serial port
+    delay: int; time in seconds
+  """
+  if brightness < ARDUINO_BRIGHTNESS_MIN:
+    logging.debug('Brightness must be >= %d.', ARDUINO_BRIGHTNESS_MIN)
+    brightness = ARDUINO_BRIGHTNESS_MIN
+  elif brightness > ARDUINO_BRIGHTNESS_MAX:
+    logging.debug('Brightness must be <= %d.', ARDUINO_BRIGHTNESS_MAX)
+    brightness = ARDUINO_BRIGHTNESS_MAX
+
+  cmd = [struct.pack('B', i) for i in [
+      ARDUINO_LIGHT_START_BYTE, int(ch), brightness]]
+  sensor_fusion_utils.arduino_send_cmd(serial_port, cmd)
+  time.sleep(delay)
+
+
+def lighting_control(lighting_cntl, lighting_ch):
+  """Establish communication with lighting controller.
+
+  lighting_ch is hard wired and must be determined from physical setup.
+
+  First initialize the port and send a test string defined by ARDUINO_TEST_CMD
+  to establish communications.
+
+  Args:
+    lighting_cntl: str to identify 'arduino' controller.
+    lighting_ch: str to identify lighting channel number.
+  Returns:
+    serial port pointer
+  """
+
+  logging.debug('Controller: %s, ch: %s', lighting_cntl, lighting_ch)
+  if lighting_cntl.lower() == 'arduino':
+    # identify port
+    arduino_serial_port = sensor_fusion_utils.serial_port_def('arduino')
+
+    # send test cmd to Arduino until cmd returns properly
+    sensor_fusion_utils.establish_serial_comm(arduino_serial_port)
+
+    # return serial port
+    return arduino_serial_port
+
+  else:
+    logging.debug('No lighting control: need to control lights manually.')
+    return None
+
+
+def set_lighting_state(arduino_serial_port, lighting_ch, state):
+  """Turn lights ON in test rig.
+
+  Args:
+    arduino_serial_port: serial port object
+    lighting_ch: str for lighting channel
+    state: str 'ON/OFF'
+  """
+  if state == 'ON':
+    level = 255
+  elif state == 'OFF':
+    level = 0
+  else:
+    raise AssertionError(f'Lighting state not defined correctly: {state}')
+
+  if arduino_serial_port:
+    set_light_brightness(lighting_ch, level, arduino_serial_port, delay=1)
+  else:
+    _ = input(f'Turn {state} lights in rig and hit ENTER to continue.')
+
diff --git a/apps/CameraITS/utils/opencv_processing_utils.py b/apps/CameraITS/utils/opencv_processing_utils.py
index 8c6a3f7..d6f10b6 100644
--- a/apps/CameraITS/utils/opencv_processing_utils.py
+++ b/apps/CameraITS/utils/opencv_processing_utils.py
@@ -63,7 +63,7 @@
 SCALE_TELE25_IN_RFOV_BOX = 0.33
 
 SQUARE_AREA_MIN_REL = 0.05  # Minimum size for square relative to image area
-SQUARE_TOL = 0.1  # Square W vs H mismatch RTOL
+SQUARE_TOL = 0.05  # Square W vs H mismatch RTOL
 
 VGA_HEIGHT = 480
 VGA_WIDTH = 640
@@ -139,7 +139,6 @@
       cam,
       props,
       log_path,
-      chart_loc=None,
       chart_file=None,
       height=None,
       distance=None,
@@ -152,7 +151,6 @@
      cam: open ITS session
      props: camera properties object
      log_path: log path to store the captured images.
-     chart_loc: chart locator arg.
      chart_file: str; absolute path to png file of chart
      height: float; height in cm of displayed chart
      distance: float; distance in cm from camera of displayed chart
@@ -166,10 +164,7 @@
     self._scale_start = scale_start or CHART_SCALE_START
     self._scale_stop = scale_stop or CHART_SCALE_STOP
     self._scale_step = scale_step or CHART_SCALE_STEP
-    self.xnorm, self.ynorm, self.wnorm, self.hnorm, self.scale = (
-        image_processing_utils.chart_located_per_argv(chart_loc))
-    if not self.xnorm:
-      self.locate(cam, props, log_path)
+    self.locate(cam, props, log_path)
 
   def _set_scale_factors_to_one(self):
     """Set scale factors to 1.0 for skipped tests."""
diff --git a/apps/CameraITS/utils/scene_change_utils.py b/apps/CameraITS/utils/scene_change_utils.py
deleted file mode 100644
index fca397b..0000000
--- a/apps/CameraITS/utils/scene_change_utils.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Copyright 2020 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-"""Utility functions for scene change test."""
-
-
-import logging
-import unittest
-
-_DARK_SCENE_THRESH = 0.2
-_FPS = 30  # Frames Per Second
-_FRAME_SHIFT_SMALL = 5  # Num of frames to shift if scene or brightness change.
-_FRAME_SHIFT_LARGE = 30  # Num of frames to shift if no change in capture.
-SCENE_CHANGE_FAIL_CODE = -1001
-SCENE_CHANGE_PASS_CODE = 1001
-
-
-def calc_timing_adjustment(converged, scene_change_flag,
-                           bright_change_flag, bright_final):
-  """Calculate timing adjustment based on converged frame and flags.
-
-  Args:
-    converged: Boolean on whether 3A converged or not.
-    scene_change_flag: Boolean for if afSceneChanged triggered.
-    bright_change_flag: Boolean for if image brightness changes.
-    bright_final: Float for average value of center patch of final frame.
-  Returns:
-    scene_change_timing_shift: Timing shift in frames.
-
-  Does timing adjustment based on input values from captured frames.
-    Truth table for 3A frame, Change flag, Bright flag, Last frame brightness
-      3, C, B, L
-      1, 1, 1, X --> PASS: 3A settled, scene and brightness change
-      1, 1, 0, X --> FAIL: 3A settled, scene change, but no brightness change
-      1, 0, 1, X --> shift FRAME_SHIFT_SMALL earlier
-      1, 0, 0, 1 --> shift FRAME_SHIFT_LARGE earlier
-      1, 0, 0, 0 --> shift FRAME_SHIFT_LARGE later
-      0, X, 1, X --> shift FRAME_SHIFT_SMALL later
-      0, X, 0, X --> FAIL: Check results of scene2/test_continuous_picture.
-    Note: these values have been found empirically for 4 different phone
-          models and 8 cameras. It is possible they may need to be tweaked as
-          more phone models become available.
-  """
-  if converged:  # 3A converges
-    if scene_change_flag:
-      if bright_change_flag:  # scene_change_flag & brightness change --> PASS
-        logging.debug('Scene & brightness change: PASS.')
-        return SCENE_CHANGE_PASS_CODE
-      else:  # scene_change_flag & no brightness change --> FAIL
-        scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
-        logging.error('Scene change, but no brightness change.')
-    else:  # No scene change flag: shift timing
-      if bright_change_flag:
-        scene_change_frame_shift = -1 * _FRAME_SHIFT_SMALL
-        logging.debug('No scene change flag, but brightness change.')
-      else:
-        logging.debug('No scene change flag, no brightness change.')
-        if bright_final < _DARK_SCENE_THRESH:
-          scene_change_frame_shift = _FRAME_SHIFT_LARGE
-          logging.debug('Scene dark entire capture.')
-        else:
-          scene_change_frame_shift = -1 * _FRAME_SHIFT_LARGE
-          logging.debug('Scene light entire capture.')
-  else:  # 3A does not converge.
-    if bright_change_flag:
-      scene_change_frame_shift = _FRAME_SHIFT_SMALL
-      logging.debug('3A does not converge, but brightness changes.')
-    else:
-      scene_change_frame_shift = SCENE_CHANGE_FAIL_CODE
-      logging.error('3A does not converge, and brightness does not change.')
-  if scene_change_frame_shift >= 0:
-    logging.debug('Shift +%d frames.', scene_change_frame_shift)
-  else:
-    logging.debug('Shift %d frames.', scene_change_frame_shift)
-  return scene_change_frame_shift
-
-
-class ItsSessionUtilsTests(unittest.TestCase):
-  """Unit tests for this module."""
-
-  def test_calc_timing_adjustment_shift(self):
-    results = {}
-    expected_results = {'1111': SCENE_CHANGE_PASS_CODE,
-                        '1110': SCENE_CHANGE_PASS_CODE,
-                        '1101': SCENE_CHANGE_FAIL_CODE,
-                        '1100': SCENE_CHANGE_FAIL_CODE,
-                        '1011': -1*_FRAME_SHIFT_SMALL,
-                        '1010': -1*_FRAME_SHIFT_SMALL,
-                        '1001': -1*_FRAME_SHIFT_LARGE,
-                        '1000': _FRAME_SHIFT_LARGE,
-                        '0111': _FRAME_SHIFT_SMALL,
-                        '0110': _FRAME_SHIFT_SMALL,
-                        '0101': SCENE_CHANGE_FAIL_CODE,
-                        '0100': SCENE_CHANGE_FAIL_CODE,
-                        '0011': _FRAME_SHIFT_SMALL,
-                        '0010': _FRAME_SHIFT_SMALL,
-                        '0001': SCENE_CHANGE_FAIL_CODE,
-                        '0000': SCENE_CHANGE_FAIL_CODE,
-                        }
-    converged_list = [1, 0]
-    scene_change_flag_list = [1, 0]
-    bright_change_flag_list = [1, 0]
-    bright_final_list = [1, 0]
-    for converged in converged_list:
-      for scene_flag in scene_change_flag_list:
-        for bright_flag in bright_change_flag_list:
-          for bright_final in bright_final_list:
-            key = f'{converged}{scene_flag}{bright_flag}{bright_final}'
-            results[key] = calc_timing_adjustment(converged, scene_flag,
-                                                  bright_flag, bright_final)
-    self.assertEqual(results, expected_results)
-
-
-if __name__ == '__main__':
-  unittest.main()
diff --git a/apps/CtsVerifier/Android.bp b/apps/CtsVerifier/Android.bp
index 628f234..7a4b2ac 100644
--- a/apps/CtsVerifier/Android.bp
+++ b/apps/CtsVerifier/Android.bp
@@ -91,7 +91,7 @@
         "DpmWrapper"
     ],
 
-    libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car"] + ["voip-common"] + ["truth-prebuilt"],
+    libs: ["telephony-common"] + ["android.test.runner.stubs"] + ["android.test.base.stubs"] + ["android.test.mock.stubs"] + ["android.car-test-stubs"] + ["voip-common"] + ["truth-prebuilt"],
 
     platform_apis: true,
 
@@ -162,6 +162,7 @@
 filegroup {
     name: "other_required_apps",
     srcs: [
+        ":CtsTileServiceApp",
         ":CtsVerifierUSBCompanion",
         ":CtsVpnFirewallAppApi23",
         ":CtsVpnFirewallAppApi24",
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 04010d5..77ff045 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -48,6 +48,7 @@
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
+    <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <uses-feature android:name="android.hardware.camera" android:required="false"/>
@@ -76,6 +77,7 @@
     <uses-permission android:name="android.permission.USE_BIOMETRIC"/>
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
@@ -3153,11 +3155,26 @@
                        android:value="multi_display_mode" />
         </activity>
 
+        <activity android:name=".qstiles.TileServiceRequestVerifierActivity"
+                  android:exported="true"
+                  android:label="@string/tiles_request_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tiles" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch:android.hardware.type.automotive" />
+            <meta-data android:name="test_required_configs" android:value="config_quick_settings_supported" />
+            <meta-data android:name="display_mode"
+                       android:value="multi_display_mode" />
+        </activity>
+
         <service android:name=".qstiles.MockTileService"
                  android:icon="@android:drawable/ic_dialog_alert"
                  android:label="@string/tile_service_name"
                  android:enabled="false"
-                  android:exported="true"
+                 android:exported="true"
                  android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE" />
@@ -3703,6 +3720,20 @@
                        android:value="single_display_mode" />
         </activity>
 
+        <activity android:name=".wifiaware.DataPathForceChannelSetupSubscribeTestActivity"
+                  android:label="@string/aware_data_path_force_channel_setup_subscribe"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
+        <activity android:name=".wifiaware.DataPathForceChannelSetupPublishTestActivity"
+                  android:label="@string/aware_data_path_force_channel_setup_publish"
+                  android:configChanges="keyboardHidden|orientation|screenSize" >
+            <meta-data android:name="display_mode"
+                       android:value="single_display_mode" />
+        </activity>
+
         <activity-alias android:name=".CtsVerifierActivity" android:label="@string/app_name"
                 android:exported="true"
                 android:targetActivity=".TestListActivity">
diff --git a/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml b/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
index 18eef3e..b40c71d 100644
--- a/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
+++ b/apps/CtsVerifier/res/drawable/display_cutout_test_button.xml
@@ -27,6 +27,16 @@
             <corners android:radius="4dp" />
         </shape>
     </item>
+    <item android:state_focused="true">
+        <shape>
+            <solid android:color="#ffbfbfbf" />
+            <padding android:left="4dp"
+                     android:top="4dp"
+                     android:right="4dp"
+                     android:bottom="4dp" />
+            <corners android:radius="4dp" />
+        </shape>
+    </item>
     <item>
         <shape>
             <solid android:color="#ff7f7f7f" />
diff --git a/apps/CtsVerifier/res/layout/audio_dev_notify.xml b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
index 6fa178d..e8f49ce 100644
--- a/apps/CtsVerifier/res/layout/audio_dev_notify.xml
+++ b/apps/CtsVerifier/res/layout/audio_dev_notify.xml
@@ -38,12 +38,6 @@
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical">
-      <Button
-          android:layout_width="match_parent"
-          android:layout_height="wrap_content"
-          android:id="@+id/audio_dev_notification_connect_clearmsgs_btn"
-          android:text="@string/audio_dev_notification_clearmsgs"/>
-
       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
diff --git a/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
index c142b15..0150ac5 100644
--- a/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
+++ b/apps/CtsVerifier/res/layout/camera_fov_calibration_photo_capture.xml
@@ -4,7 +4,7 @@
     android:layout_height="match_parent"
     tools:context=".PhotoCaptureActivity" >
 
-    <SurfaceView
+    <TextureView
         android:id="@+id/camera_fov_camera_preview"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
diff --git a/apps/CtsVerifier/res/layout/tiles_item.xml b/apps/CtsVerifier/res/layout/tiles_item.xml
index f2adaa4..9496a70 100644
--- a/apps/CtsVerifier/res/layout/tiles_item.xml
+++ b/apps/CtsVerifier/res/layout/tiles_item.xml
@@ -38,10 +38,25 @@
         android:layout_alignParentTop="true"
         android:layout_toRightOf="@id/tiles_status"/>
 
+    <Button
+        android:id="@+id/tiles_action_request"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:visibility="gone"
+        android:layout_toRightOf="@id/tiles_status"
+        android:layout_below="@id/tiles_instructions"
+        android:layout_alignParentRight="true"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:onClick="actionPressed"
+        android:text="Start request"
+        android:enabled="false"
+        />
+
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_below="@id/tiles_instructions"
+        android:layout_below="@id/tiles_action_request"
         android:layout_toRightOf="@id/tiles_status"
         android:layout_alignParentRight="true">
 
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 5771068..f645f91 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2123,6 +2123,7 @@
     <string name="aware_dp_ib_open_solicited_accept_any">Data Path: Open: Solicited/Active: accept any peer</string>
     <string name="aware_dp_ib_passphrase_solicited_accept_any">Data Path: Passphrase: Solicited/Active: accept any peer</string>
     <string name="aware_dp_ib_pmk_solicited_accept_any">Data Path: PMK: Solicited/Active: accept any peer</string>
+    <string name="aware_dp_ib_force_channel_setup">Data Path: force channel setup</string>
     <string name="aware_discovery_ranging">Discovery with Ranging</string>
     <string name="aware_publish">Publish</string>
     <string name="aware_subscribe">Subscribe</string>
@@ -2213,6 +2214,10 @@
     <string name="aware_discovery_ranging_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Discovery with Ranging\' / \'Subscribe\' test.</string>
     <string name="aware_discovery_ranging_subscribe">Discovery with Ranging: Subscribe</string>
 
+    <string name="aware_data_path_force_channel_setup_publish">Data Path: force channel setup Publish</string>
+    <string name="aware_data_path_force_channel_setup_publish_info">The publisher is now ready.\n\nOn the other device: start the \'Data Path: force channel setup\' / \'Subscribe\' test.</string>
+    <string name="aware_data_path_force_channel_setup_subscribe">Data Path: force channel setup Subscribe</string>
+
     <string name="camera_fov_calibration">Camera FOV Calibration</string>
     <string name="camera_fov_calibration_done">Done</string>
     <string name="camera_fov_general_settings">General settings</string>
@@ -2384,6 +2389,11 @@
         notifications, fully expand the notification display and verify that the \"Person A\"
         notification appears before the "\Non-Person Notification\".
         If this device does not support conversation notifications or does not group them together, press Pass.</string>
+    <string name="hun_display">If this device supports heads-up notifications,
+        verify that a notification just peeked on screen, displaying all of the following: small
+        icon, large icon, notification title, notification text, and two action buttons.
+        If this device does not support heads-up notifications, press Pass.</string>
+    <string name="action">Action %1$d</string>
     <string name="tile_service_name">Tile Service for CTS Verifier</string>
     <string name="tiles_test">Tile Service Test</string>
     <string name="tiles_info">This test checks that a Tile Service added by a third party
@@ -2396,6 +2406,46 @@
     <string name="tiles_in_customizer">Open Quick Settings and click the button to customize Quick
         Settings. Check that the Tile Service for CTS Verifier is available to be added</string>
     <string name="tiles_removing_tile">Check that Tile Service is disabled</string>
+    <string name="tile_request_service_name">Request Tile Service</string>
+    <string name="tile_request_helper_app_name">CtsTileServiceApp</string>
+    <string name="tiles_request_test">Tile Service Request Test</string>
+    <string name="tiles_request_info">This test checks that a request to add a TileService is
+    shown correctly to the user and the correct answer is relayed to the app.</string>
+    <string name="tiles_request_uninstall">This will verify that the helper app is not installed.
+    If that is correct, the test will pass automatically. If the app is installed, press the button
+    and you will be prompted for confirmation to uninstall the app.</string>
+    <string name="tiles_request_install">You should have received a helper app apk with this CTS
+    Verifier package. Its name is \"CtsTileServiceApp\". Install the test app, by running\n
+    \"adb install /path/to/CtsTileServiceApp.apk\".\n
+    Pass the test if the install was successful. Fail it otherwise.
+    </string>
+    <string name="tiles_request_install_verify">This will automatically verify that the helper app
+    is correctly installed.</string>
+    <string name="tiles_request_tile_not_present">Open Quick Settings and pass this test
+    if there are no tiles named %1$s in any page.</string>
+    <string name="tiles_request_dismissed">After pressing \"Start request\", a dialog will appear
+        requesting to add a quick settings tile. When it appears, dismiss it by tapping outside of
+        it, or going back.</string>
+    <string name="tiles_request_answer_no">After pressing \"Start request\", a dialog will appear
+        requesting to add a quick settings tile. When it appears, tap the option in the dialog to
+        not add the tile.</string>
+    <string name="tiles_request_correct_info">After pressing \"Start request\", a dialog will appear
+        requesting to add a quick settings tile. When it appears, verify the following information
+        in the dialog:\n
+  1. The name of the app requesting to add the tile is %1$s.\n
+  2. The label in the tile is %2$s.\n
+After verifying, tap the option in the dialog to not add the tile.\n
+When the buttons are enabled, pass this test if the information in the dialog was correct.
+    </string>
+    <string name="tiles_request_answer_yes">After pressing \"Start request\", a dialog will appear
+        requesting to add a quick settings tile. When it appears, tap the option in the dialog to
+        add the tile.
+    </string>
+    <string name="tiles_request_tile_present">Open Quick Settings and pass this test if a tile named
+        %1$s is visible in some page.
+    </string>
+    <string name="tiles_request_check_tile_already_added">Checking tile already added response
+    </string>
     <string name="vr_tests">VR Tests</string>
     <string name="test_category_vr">VR</string>
     <string name="vr_test_title">VR Listener Test</string>
@@ -5009,16 +5059,13 @@
     <string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
 
     <!--  Audio Devices Notifications Tests -->
+    <string name="audio_devices_notification_instructions">
+          Connect and disconnect a wired headset.
+          Note if the appropriate notification messages appear below.
+          The \"Pass\" button will be enabled if device connect and disconnect messages are received.
+    </string>
     <string name="audio_out_devices_notifications_test">Audio Output Devices Notifications Test</string>
-    <string name="audio_out_devices_notification_instructions">
-          Click the "Clear Messages" button then connect and disconnect a wired headset.
-          Note if the appropriate notification messages appear below.
-    </string>
     <string name="audio_in_devices_notifications_test">Audio Input Devices Notifications Test</string>
-    <string name="audio_in_devices_notification_instructions">
-          Click the "Clear Messages" button then connect and disconnect a microphone or wired headset.
-          Note if the appropriate notification messages appear below.
-    </string>
     <string name="audio_dev_notification_clearmsgs">Clear Messages</string>
     <string name="audio_dev_notification_connectMsg">CONNECT DETECTED</string>
     <string name="audio_dev_notification_disconnectMsg">DISCONNECT DETECTED</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
index ab02210..0f352f5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/AbstractTestListActivity.java
@@ -21,6 +21,7 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.util.Log;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.Window;
@@ -46,6 +47,8 @@
     // Whether test case was executed through automation.
     protected boolean mIsAutomated;
 
+    protected final String mTag = getClass().getSimpleName();
+
     protected void setTestListAdapter(TestListAdapter adapter) {
         mAdapter = adapter;
         setListAdapter(mAdapter);
@@ -128,6 +131,8 @@
     /** Override this in subclasses instead of onListItemClick */
     protected void handleItemClick(ListView listView, View view, int position, long id) {
         Intent intent = getIntent(position);
+        Log.i(mTag, "Launching activity with " + IntentDrivenTestActivity.toString(this, intent));
+
         startActivityForResult(intent, LAUNCH_TEST_REQUEST_CODE);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
index cc072a1..a1c1a0e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/IntentDrivenTestActivity.java
@@ -4,10 +4,12 @@
 
 import android.app.AlertDialog;
 import android.content.ActivityNotFoundException;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -15,6 +17,11 @@
 import android.widget.Button;
 import android.widget.TextView;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
 /**
  *  A generic activity for intent based tests.
  *
@@ -28,32 +35,30 @@
  *  will dynamically create the intent when the button is clicked based on the test id and the
  *  button that was clicked.
  */
-public class IntentDrivenTestActivity extends PassFailButtons.Activity implements OnClickListener {
-    private static final String TAG = "IntentDrivenTestActivity";
+public final class IntentDrivenTestActivity extends PassFailButtons.Activity
+        implements OnClickListener {
+
+    private static final String TAG = IntentDrivenTestActivity.class.getSimpleName();
 
     public static final String EXTRA_ID = "id";
     public static final String EXTRA_TITLE = "title";
     public static final String EXTRA_INFO = "info";
     public static final String EXTRA_BUTTONS = "buttons";
 
+    private static final String[] REQUIRED_EXTRAS = {
+            EXTRA_ID, EXTRA_TITLE, EXTRA_INFO, EXTRA_BUTTONS
+    };
+
     private String mTestId;
     private ButtonInfo[] mButtonInfos;
 
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.intent_driven_test);
         setPassFailButtonClickListeners();
 
-        final Intent intent = getIntent();
-        if (!intent.hasExtra(EXTRA_ID)
-                || !intent.hasExtra(EXTRA_TITLE)
-                || !intent.hasExtra(EXTRA_INFO)
-                || !intent.hasExtra(EXTRA_BUTTONS)) {
-            throw new IllegalArgumentException(
-                    "Intent must have EXTRA_ID, EXTRA_TITLE, EXTRA_INFO & EXTRA_BUTTONS");
-        }
+        final Intent intent = checkAndGetIntent();
 
         mTestId = intent.getStringExtra(EXTRA_ID);
         setTitle(intent.getIntExtra(EXTRA_TITLE, -1));
@@ -130,7 +135,7 @@
         return mTestId;
     }
 
-    public static class TestInfo {
+    public static final class TestInfo {
         private final String mTestId;
         private final int mTitle;
         private final int mInfoText;
@@ -145,7 +150,7 @@
             if (buttons.length > 2) {
                 throw new RuntimeException("Too many buttons");
             }
-            mTestId = testId;
+            mTestId = nonEmpty("testId", testId);
             mTitle = title;
             mInfoText = infoText;
             mButtons = buttons;
@@ -168,7 +173,7 @@
         }
     }
 
-    public static class ButtonInfo implements Parcelable {
+    public static final class ButtonInfo implements Parcelable {
         private final int mButtonText;
         private final Intent[] mIntents;
         private final String mIntentFactoryClassName;
@@ -238,4 +243,57 @@
     public interface IntentFactory {
         Intent[] createIntents(String testId, int buttonText);
     }
+
+    public static Intent newIntent(Context context, String testId, int titleResId,
+            int infoResId, ButtonInfo[] buttons) {
+        Intent intent = new Intent(context, IntentDrivenTestActivity.class)
+                .putExtra(IntentDrivenTestActivity.EXTRA_ID, nonEmpty("testId", testId))
+                .putExtra(IntentDrivenTestActivity.EXTRA_TITLE, titleResId)
+                .putExtra(IntentDrivenTestActivity.EXTRA_INFO, infoResId)
+                .putExtra(IntentDrivenTestActivity.EXTRA_BUTTONS,
+                        Objects.requireNonNull(buttons, "buttons cannot be null"));
+        Log.d(TAG, "Added 4 required extras to  " + toString(context, intent));
+        return intent;
+    }
+
+    private static String nonEmpty(String name, String value) {
+        if (TextUtils.isEmpty(value)) {
+            throw new IllegalArgumentException(name + " cannot be null or empty: '" + value + "'");
+        }
+        return value;
+    }
+
+    private Intent checkAndGetIntent() {
+        Intent intent = getIntent();
+        List<String> missingExtras = new ArrayList<>();
+        for (String extra : REQUIRED_EXTRAS) {
+            if (!intent.hasExtra(extra)) {
+                missingExtras.add(extra);
+            }
+        }
+        if (!missingExtras.isEmpty()) {
+            throw new IllegalArgumentException(toString(this, intent) + ") is missing "
+                    + missingExtras.size() + " required extras: "
+                    + Arrays.toString(REQUIRED_EXTRAS));
+        }
+        return intent;
+    }
+
+    public static String toString(Context context, Intent intent) {
+        return new StringBuilder("Intent["
+                + "address=").append(System.identityHashCode(intent))
+                .append(", numberExtras=").append(intent.getExtras().size())
+                .append(", extraKeys=").append(new ArrayList<>(intent.getExtras().keySet()))
+                .append(", testId=")
+                .append(intent.hasExtra(EXTRA_ID) ? intent.getStringExtra(EXTRA_ID) : "N/A")
+                .append(", title=").append(intent.hasExtra(EXTRA_TITLE)
+                                ? context.getString(intent.getIntExtra(EXTRA_TITLE, -1))
+                                : "N/A")
+                .append(intent.hasExtra(EXTRA_INFO) ? ", has_info" : ", no_info")
+                .append(", numberButtons=").append(intent.hasExtra(EXTRA_BUTTONS)
+                                ? intent.getParcelableArrayExtra(EXTRA_BUTTONS).length
+                                : 0)
+                .append(", rawIntent=").append(intent)
+                .append(']').toString();
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index 4bd642d..0294ff7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -70,7 +70,10 @@
     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
 
     // ReportLog file for CTS-Verifier. The "stream" name gets mapped to the test class name.
-    private static final String REPORT_LOG_NAME = "CTS-Verifier-Log";
+    public static final String GENERAL_TESTS_REPORT_LOG_NAME = "CtsVerifierGeneralTestCases";
+    public static final String AUDIO_TESTS_REPORT_LOG_NAME = "CtsVerifierAudioTestCases";
+
+    private static final String SECTION_UNDEFINED = "undefined_section_name";
 
     // Interface mostly for making documentation and refactoring easier...
     public interface PassFailActivity {
@@ -112,10 +115,16 @@
         void setTestResultAndFinish(boolean passed);
 
         /**
-         * @return A unique name (derived from the test class name) to serve as a section
-         * header in the CtsVerifierReportLog file.
+         * @return The name of the file to store the (suite of) ReportLog information.
          */
-        String getReportSectionName();
+        public String getReportFileName();
+
+        /**
+         * @return A unique name to serve as a section header in the CtsVerifierReportLog file.
+         * Tests need to conform to the underscore_delineated_name standard for use with
+         * the protobuff/json ReportLog parsing in Google3
+         */
+        public String getReportSectionName();
 
         /**
          * Test subclasses can override this to record their CtsVerifierReportLogs.
@@ -138,7 +147,7 @@
         private final TestResultHistoryCollection mHistoryCollection;
 
         public Activity() {
-            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
             this.mHistoryCollection = new TestResultHistoryCollection();
         }
 
@@ -202,9 +211,15 @@
             return mReportLog;
         }
 
+        /**
+         * @return The name of the file to store the (suite of) ReportLog information.
+         */
         @Override
-        public final String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+        @Override
+        public String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
         }
 
         @Override
@@ -240,7 +255,7 @@
         private final TestResultHistoryCollection mHistoryCollection;
 
         public ListActivity() {
-            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
             this.mHistoryCollection = new TestResultHistoryCollection();
         }
 
@@ -286,9 +301,15 @@
             return mReportLog;
         }
 
+        /**
+         * @return The name of the file to store the (suite of) ReportLog information.
+         */
         @Override
-        public final String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+        @Override
+        public String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
         }
 
         @Override
@@ -326,9 +347,9 @@
         public TestListActivity() {
             // TODO(b/186555602): temporary hack^H^H^H^H workaround to fix crash
             // This DOES NOT in fact fix that bug.
-            // if (true) this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, "42"); else
+            // if (true) this.mReportLog = new CtsVerifierReportLog(b/186555602, "42"); else
 
-            this.mReportLog = new CtsVerifierReportLog(REPORT_LOG_NAME, getReportSectionName());
+            this.mReportLog = new CtsVerifierReportLog(getReportFileName(), getReportSectionName());
         }
 
         @Override
@@ -373,11 +394,18 @@
             return mReportLog;
         }
 
+        /**
+         * @return The name of the file to store the (suite of) ReportLog information.
+         */
         @Override
-        public final String getReportSectionName() {
-            return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+        public String getReportFileName() { return GENERAL_TESTS_REPORT_LOG_NAME; }
+
+        @Override
+        public String getReportSectionName() {
+            return setTestNameSuffix(sCurrentDisplayMode, SECTION_UNDEFINED);
         }
 
+
         /**
          * Get existing test history to aggregate.
          */
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
index 6233f2c..9ff7028 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AnalogHeadsetAudioActivity.java
@@ -52,6 +52,9 @@
 import org.hyphonate.megaaudio.player.PlayerBuilder;
 import org.hyphonate.megaaudio.player.sources.SinAudioSourceProvider;
 
+import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
+import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+
 public class AnalogHeadsetAudioActivity
         extends PassFailButtons.Activity
         implements View.OnClickListener {
@@ -105,6 +108,17 @@
 
     JavaPlayer mAudioPlayer;
 
+    // ReportLog Schema
+    private static final String SECTION_ANALOG_HEADSET = "analog_headset_activity";
+    private static final String KEY_HAS_HEADSET_PORT = "has_headset_port";
+    private static final String KEY_HEADSET_PLUG_INTENT_STATE = "intent_received_state";
+    private static final String KEY_CLAIMS_HEADSET_PORT = "claims_headset_port";
+    private static final String KEY_HEADSET_CONNECTED = "headset_connected";
+    private static final String KEY_KEYCODE_HEADSETHOOK = "keycode_headset_hook";
+    private static final String KEY_KEYCODE_PLAY_PAUSE = "keycode_play_pause";
+    private static final String KEY_KEYCODE_VOLUME_UP = "keycode_volume_up";
+    private static final String KEY_KEYCODE_VOLUME_DOWN = "keycode_volume_down";
+
     public AnalogHeadsetAudioActivity() {
         super();
     }
@@ -186,10 +200,26 @@
         }
     }
 
+    //
+    // PassFailButtons Overrides
+    //
+    @Override
+    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+    @Override
+    public final String getReportSectionName() {
+        return setTestNameSuffix(sCurrentDisplayMode, SECTION_ANALOG_HEADSET);
+    }
+
+    @Override
+    public void recordTestResults() {
+        getReportLog().submit();
+    }
+
     private void reportHeadsetPort(boolean has) {
         mHasHeadsetPort = has;
         getReportLog().addValue(
-                "User Reports Headset Port",
+                KEY_HAS_HEADSET_PORT,
                 has ? 1 : 0,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
@@ -247,7 +277,7 @@
         }
 
         getReportLog().addValue(
-                "ACTION_HEADSET_PLUG Intent Received. State: ",
+                KEY_HEADSET_PLUG_INTENT_STATE,
                 state,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
@@ -264,7 +294,7 @@
         getPassButton().setEnabled(calculatePass());
 
         getReportLog().addValue(
-                "User reported headset/headphones playback",
+                KEY_CLAIMS_HEADSET_PORT,
                 success ? 1 : 0,
                 ResultType.NEUTRAL,
                 ResultUnit.NONE);
@@ -389,18 +419,15 @@
             if (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
                     devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
                 mHeadsetDeviceInfo = devInfo;
-
-                getReportLog().addValue(
-                        (devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET
-                                ? "Headset" : "Headphones") + " connected",
-                        0,
-                        ResultType.NEUTRAL,
-                        ResultUnit.NONE);
                 break;
             }
         }
 
-        reportHeadsetPort(mHeadsetDeviceInfo != null);
+        getReportLog().addValue(
+                KEY_HEADSET_CONNECTED,
+                mHeadsetDeviceInfo != null ? 1 : 0,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
 
         showConnectedDevice();
     }
@@ -445,7 +472,7 @@
                 showKeyMessagesState();
                 getPassButton().setEnabled(calculatePass());
                 getReportLog().addValue(
-                        "KEYCODE_HEADSETHOOK", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+                        KEY_KEYCODE_HEADSETHOOK, 1, ResultType.NEUTRAL, ResultUnit.NONE);
                 break;
 
             case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -453,7 +480,7 @@
                 showKeyMessagesState();
                 getPassButton().setEnabled(calculatePass());
                 getReportLog().addValue(
-                        "KEYCODE_MEDIA_PLAY_PAUSE", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+                        KEY_KEYCODE_PLAY_PAUSE, 1, ResultType.NEUTRAL, ResultUnit.NONE);
                 break;
 
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -461,7 +488,7 @@
                 showKeyMessagesState();
                 getPassButton().setEnabled(calculatePass());
                 getReportLog().addValue(
-                        "KEYCODE_VOLUME_UP", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+                        KEY_KEYCODE_VOLUME_UP, 1, ResultType.NEUTRAL, ResultUnit.NONE);
                 break;
 
             case KeyEvent.KEYCODE_VOLUME_DOWN:
@@ -469,7 +496,7 @@
                 showKeyMessagesState();
                 getPassButton().setEnabled(calculatePass());
                 getReportLog().addValue(
-                        "KEYCODE_VOLUME_DOWN", 1, ResultType.NEUTRAL, ResultUnit.NONE);
+                        KEY_KEYCODE_VOLUME_DOWN, 1, ResultType.NEUTRAL, ResultUnit.NONE);
                 break;
         }
         return super.onKeyDown(keyCode, event);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
index 64c2314..6a7d92e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputDeviceNotificationsActivity.java
@@ -16,22 +16,16 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.R;
-
 import android.content.Context;
-
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-
 import android.os.Bundle;
-
 import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.cts.verifier.R;
+
 /**
  * Tests Audio Device Connection events for output by prompting the user to insert/remove a
  * wired headset (or microphone) and noting the presence (or absence) of notifications.
@@ -41,13 +35,24 @@
 
     TextView mConnectView;
     TextView mDisconnectView;
-    Button mClearMsgsBtn;
+    TextView mInfoView;
+
+    boolean mHandledInitialAddedMessage = false;
+    boolean mConnectReceived = false;
+    boolean mDisconnectReceived = false;
 
     private class TestAudioDeviceCallback extends AudioDeviceCallback {
         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            // we will get this message when we setup the handler, so ignore the first one.
+            if (!mHandledInitialAddedMessage) {
+                mHandledInitialAddedMessage = true;
+                return;
+            }
             if (addedDevices.length != 0) {
                 mConnectView.setText(
                     mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
+                mConnectReceived = true;
+                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
             }
         }
 
@@ -56,35 +61,25 @@
                 mDisconnectView.setText(
                     mContext.getResources().getString(
                         R.string.audio_dev_notification_disconnectMsg));
+                mDisconnectReceived = true;
+                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
             }
         }
     }
 
     @Override
-    protected void enableTestButtons(boolean enabled) {
-        // Nothing to do.
-    }
-
-    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_dev_notify);
 
         mContext = this;
 
-        mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
-        mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
+        mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
+        mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
 
-        ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
-                R.string.audio_in_devices_notification_instructions));
-
-        mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
-        mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View v) {
-                mConnectView.setText("");
-                mDisconnectView.setText("");
-            }
-        });
+        mInfoView = (TextView) findViewById(R.id.info_text);
+        mInfoView.setText(mContext.getResources().getString(
+                R.string.audio_devices_notification_instructions));
 
         AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
         audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -94,4 +89,9 @@
 
         setPassFailButtonClickListeners();
     }
+
+    @Override
+    protected void enableTestButtons(boolean enabled) {
+        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
index 4b2d213..4895cde 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioInputRoutingNotificationsActivity.java
@@ -16,30 +16,22 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.R;
-
 import android.content.Context;
-
-import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
-import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.AudioRecord;
-
 import android.os.Bundle;
 import android.os.Handler;
-
 import android.util.Log;
-
 import android.view.View;
 import android.view.View.OnClickListener;
-
 import android.widget.Button;
 import android.widget.TextView;
 
-import org.hyphonate.megaaudio.recorder.RecorderBuilder;
-import org.hyphonate.megaaudio.recorder.Recorder;
+import com.android.cts.verifier.R;
+
 import org.hyphonate.megaaudio.recorder.JavaRecorder;
+import org.hyphonate.megaaudio.recorder.Recorder;
+import org.hyphonate.megaaudio.recorder.RecorderBuilder;
 import org.hyphonate.megaaudio.recorder.sinks.NopAudioSinkProvider;
 
 /*
@@ -50,10 +42,11 @@
 
     Button recordBtn;
     Button stopBtn;
+    TextView mInfoView;
 
     Context mContext;
 
-    int mNumRecordNotifications = 0;
+    int mNumRoutingNotifications;
 
     OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
@@ -61,7 +54,12 @@
     static final int SAMPLE_RATE = 48000;
     int mNumFrames;
 
-    JavaRecorder mAudioRecorder;
+    private JavaRecorder mAudioRecorder;
+    private AudioRecordRoutingChangeListener mRouteChangeListener;
+    private boolean mIsRecording;
+
+    // ignore messages sent as a consequence of starting the player
+    private static final int NUM_IGNORE_MESSAGES = 2;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
@@ -72,26 +70,50 @@
 
             switch (v.getId()) {
                 case R.id.audio_routingnotification_recordBtn:
-                {
-                     mAudioRecorder.startStream();
-
-                    AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
-                    audioRecord.addOnRoutingChangedListener(
-                            new AudioRecordRoutingChangeListener(), new Handler());
-
-                }
+                    startRecording();
                     break;
 
                 case R.id.audio_routingnotification_recordStopBtn:
-                    mAudioRecorder.stopStream();
                     break;
             }
         }
     }
 
+    private void startRecording() {
+        if (!mIsRecording) {
+            mNumRoutingNotifications = 0;
+
+            mAudioRecorder.startStream();
+
+            AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+            audioRecord.addOnRoutingChangedListener(mRouteChangeListener,
+                    new Handler());
+
+            mIsRecording = true;
+            enableRecordButtons(!mIsRecording, mIsRecording);
+        }
+    }
+
+    private void stopRecording() {
+        if (mIsRecording) {
+            mAudioRecorder.stopStream();
+
+            AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+            audioRecord.removeOnRoutingChangedListener(mRouteChangeListener);
+
+            mIsRecording = false;
+            enableRecordButtons(!mIsRecording, mIsRecording);
+        }
+    }
+
     private class AudioRecordRoutingChangeListener implements AudioRecord.OnRoutingChangedListener {
         public void onRoutingChanged(AudioRecord audioRecord) {
-            mNumRecordNotifications++;
+            // Starting recording triggers routing messages, so ignore the first one.
+            mNumRoutingNotifications++;
+            if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
+                return;
+            }
+
             TextView textView =
                     (TextView)findViewById(R.id.audio_routingnotification_audioRecord_change);
             String msg = mContext.getResources().getString(
@@ -101,13 +123,20 @@
             int deviceType = routedDevice != null ? routedDevice.getType() : -1;
             textView.setText(msg + " - " +
                              deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
-                             " - " + mNumRecordNotifications);
+                             " - " + mNumRoutingNotifications);
+            getPassButton().setEnabled(true);
         }
     }
 
+    @Override
     protected void enableTestButtons(boolean enabled) {
-        recordBtn.setEnabled(enabled);
-        stopBtn.setEnabled(enabled);
+        enableRecordButtons(!mIsRecording, mIsRecording);
+        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    void enableRecordButtons(boolean enableRecord, boolean enableStop) {
+        recordBtn.setEnabled(enableRecord);
+        stopBtn.setEnabled(enableStop);
     }
 
     @Override
@@ -116,11 +145,15 @@
         setContentView(R.layout.audio_input_routingnotifications_test);
 
         Button btn;
-        recordBtn = (Button)findViewById(R.id.audio_routingnotification_recordBtn);
+        recordBtn = (Button) findViewById(R.id.audio_routingnotification_recordBtn);
         recordBtn.setOnClickListener(mBtnClickListener);
-        stopBtn = (Button)findViewById(R.id.audio_routingnotification_recordStopBtn);
+        stopBtn = (Button) findViewById(R.id.audio_routingnotification_recordStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
+        enableRecordButtons(false, false);
+
+        mInfoView = (TextView) findViewById(R.id.info_text);
+
         mContext = this;
 
         // Setup Recorder
@@ -137,17 +170,18 @@
             Log.e(TAG, "Failed MegaRecorder build.");
         }
 
+        mRouteChangeListener = new AudioRecordRoutingChangeListener();
+        AudioRecord audioRecord = mAudioRecorder.getAudioRecord();
+        audioRecord.addOnRoutingChangedListener(mRouteChangeListener, new Handler());
+
         // "Honor System" buttons
         super.setup();
-
         setPassFailButtonClickListeners();
     }
 
     @Override
     public void onBackPressed () {
-        if (mAudioRecorder != null) {
-            mAudioRecorder.stopStream();
-        }
+        stopRecording();
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
deleted file mode 100644
index 78e34d3..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackBaseActivity.java
+++ /dev/null
@@ -1,686 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.audio;
-
-import android.app.AlertDialog;
-import android.media.AudioDeviceCallback;
-import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
-import android.media.MediaRecorder;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
-import android.util.Log;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ProgressBar;
-import android.widget.SeekBar;
-import android.widget.TextView;
-
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
-import com.android.cts.verifier.audio.audiolib.StatUtils;
-import com.android.cts.verifier.audio.audiolib.AudioUtils;
-import com.android.cts.verifier.CtsVerifierReportLog;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
-import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
-
-/**
- * Base class for testing activitiees that require audio loopback hardware..
- */
-public class AudioLoopbackBaseActivity extends PassFailButtons.Activity {
-    private static final String TAG = "AudioLoopbackBaseActivity";
-
-    // JNI load
-    static {
-        try {
-            System.loadLibrary("audioloopback_jni");
-        } catch (UnsatisfiedLinkError e) {
-            Log.e(TAG, "Error loading Audio Loopback JNI library");
-            Log.e(TAG, "e: " + e);
-            e.printStackTrace();
-        }
-
-        /* TODO: gracefully fail/notify if the library can't be loaded */
-    }
-    protected AudioManager mAudioManager;
-
-    // UI
-    TextView mInputDeviceTxt;
-    TextView mOutputDeviceTxt;
-
-    TextView mAudioLevelText;
-    SeekBar mAudioLevelSeekbar;
-
-    TextView mTestPathTxt;
-
-    TextView mResultText;
-    ProgressBar mProgressBar;
-    int mMaxLevel;
-
-    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
-    protected Button mTestButton;
-
-    String mYesString;
-    String mNoString;
-
-    // These flags determine the maximum allowed latency
-    private boolean mClaimsProAudio;
-    private boolean mClaimsOutput;
-    private boolean mClaimsInput;
-
-    // Useful info
-    private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
-    private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
-
-    // Peripheral(s)
-    boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
-    AudioDeviceInfo mOutputDevInfo;
-    AudioDeviceInfo mInputDevInfo;
-
-    protected static final int TESTPERIPHERAL_INVALID       = -1;
-    protected static final int TESTPERIPHERAL_NONE          = 0;
-    protected static final int TESTPERIPHERAL_ANALOG_JACK   = 1;
-    protected static final int TESTPERIPHERAL_USB           = 2;
-    protected static final int TESTPERIPHERAL_DEVICE        = 3; // device speaker + mic
-    protected int mTestPeripheral = TESTPERIPHERAL_NONE;
-
-    // Loopback Logic
-    NativeAnalyzerThread mNativeAnalyzerThread = null;
-
-    protected static final int NUM_TEST_PHASES = 5;
-    protected int mTestPhase = 0;
-
-    protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
-    protected double[] mConfidence = new double[NUM_TEST_PHASES];
-
-    protected double mMeanLatencyMillis;
-    protected double mMeanAbsoluteDeviation;
-    protected double mMeanConfidence;
-
-    protected static final double CONFIDENCE_THRESHOLD = 0.6;
-    // impossibly low latencies (indicating something in the test went wrong).
-    protected static final float EPSILON = 1.0f;
-    protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
-    protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
-    protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
-    protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
-    protected static final double BASIC_MUST_LATENCY_MS = 800.0;
-    protected double mMustLatency;
-    protected double mRecommendedLatency;
-
-    // The audio stream callback threads should stop and close
-    // in less than a few hundred msec. This is a generous timeout value.
-    private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
-
-    //
-    // Common UI Handling
-    //
-    private void connectLoopbackUI() {
-        // Connected Device
-        mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
-        mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
-
-        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
-        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
-        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setMax(mMaxLevel);
-        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
-        refreshLevel();
-
-        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
-            @Override
-            public void onStopTrackingTouch(SeekBar seekBar) {}
-
-            @Override
-            public void onStartTrackingTouch(SeekBar seekBar) {}
-
-            @Override
-            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
-                        progress, 0);
-                Log.i(TAG,"Level set to: " + progress);
-                refreshLevel();
-            }
-        });
-
-        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
-        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
-        showWait(false);
-    }
-
-    //
-    // Peripheral Connection Logic
-    //
-    protected void scanPeripheralList(AudioDeviceInfo[] devices) {
-        // CDD Section C-1-3: USB port, host-mode support
-
-        // Can't just use the first record because then we will only get
-        // Source OR sink, not both even on devices that are both.
-        mOutputDevInfo = null;
-        mInputDevInfo = null;
-
-        // Any valid peripherals
-        // Do we leave in the Headset test to support a USB-Dongle?
-        for (AudioDeviceInfo devInfo : devices) {
-            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
-                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
-                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
-                    devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
-                if (devInfo.isSink()) {
-                    mOutputDevInfo = devInfo;
-                }
-                if (devInfo.isSource()) {
-                    mInputDevInfo = devInfo;
-                }
-            }  else {
-                handleDeviceConnection(devInfo);
-            }
-        }
-
-        // need BOTH input and output to test
-        mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
-        calculateTestPeripheral();
-        showConnectedAudioPeripheral();
-        calculateLatencyThresholds();
-        displayLatencyThresholds();
-    }
-
-    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
-        // NOP
-    }
-
-    private class ConnectListener extends AudioDeviceCallback {
-        /*package*/ ConnectListener() {}
-
-        //
-        // AudioDevicesManager.OnDeviceConnectionListener
-        //
-        @Override
-        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
-
-        @Override
-        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
-            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
-        }
-    }
-
-    private void calculateTestPeripheral() {
-        if (!mIsPeripheralAttached) {
-            if ((mOutputDevInfo != null && mInputDevInfo == null)
-                || (mOutputDevInfo == null && mInputDevInfo != null)) {
-                mTestPeripheral = TESTPERIPHERAL_INVALID;
-            } else {
-                mTestPeripheral = TESTPERIPHERAL_DEVICE;
-            }
-        } else if (!areIODevicesOnePeripheral()) {
-            mTestPeripheral = TESTPERIPHERAL_INVALID;
-        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
-                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
-            mTestPeripheral = TESTPERIPHERAL_USB;
-        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
-                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
-            mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
-        } else {
-            // Huh?
-            Log.e(TAG, "No valid peripheral found!?");
-            mTestPeripheral = TESTPERIPHERAL_NONE;
-        }
-    }
-
-    private boolean isPeripheralValidForTest() {
-        return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
-                    || mTestPeripheral == TESTPERIPHERAL_USB;
-    }
-
-    private void showConnectedAudioPeripheral() {
-        mInputDeviceTxt.setText(
-                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
-                        : "Not connected");
-        mOutputDeviceTxt.setText(
-                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
-                        : "Not connected");
-
-        String pathName;
-        switch (mTestPeripheral) {
-            case TESTPERIPHERAL_INVALID:
-                pathName = "Invalid Test Peripheral";
-                break;
-
-            case TESTPERIPHERAL_ANALOG_JACK:
-                pathName = "Headset Jack";
-                break;
-
-            case TESTPERIPHERAL_USB:
-                pathName = "USB";
-                break;
-
-            case TESTPERIPHERAL_DEVICE:
-                pathName = "Device Speaker + Microphone";
-                break;
-
-            case TESTPERIPHERAL_NONE:
-            default:
-                pathName = "Error. Unknown Test Path";
-                break;
-        }
-        mTestPathTxt.setText(pathName);
-        mTestButton.setEnabled(
-                mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
-
-    }
-
-    private boolean areIODevicesOnePeripheral() {
-        if (mOutputDevInfo == null || mInputDevInfo == null) {
-            return false;
-        }
-
-        return mOutputDevInfo.getProductName().toString().equals(
-                mInputDevInfo.getProductName().toString());
-    }
-
-    private void calculateLatencyThresholds() {
-        switch (mTestPeripheral) {
-            case TESTPERIPHERAL_ANALOG_JACK:
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency =  mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_USB:
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_DEVICE:
-                // This isn't a valid case so we won't pass it, but it can be run
-                mRecommendedLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = mClaimsProAudio
-                        ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
-                break;
-
-            case TESTPERIPHERAL_NONE:
-            default:
-                mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
-                mMustLatency = BASIC_MUST_LATENCY_MS;
-                break;
-        }
-    }
-
-    private void displayLatencyThresholds() {
-        if (isPeripheralValidForTest()) {
-            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
-            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
-                    "" + mRecommendedLatency);
-        } else {
-            String naStr = getResources().getString(R.string.audio_proaudio_NA);
-            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
-            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
-        }
-    }
-
-    /**
-     * refresh Audio Level seekbar and text
-     */
-    private void refreshLevel() {
-        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
-        mAudioLevelSeekbar.setProgress(currentLevel);
-
-        String levelText = String.format("%s: %d/%d",
-                getResources().getString(R.string.audio_loopback_level_text),
-                currentLevel, mMaxLevel);
-        mAudioLevelText.setText(levelText);
-    }
-
-    //
-    // show active progress bar
-    //
-    protected void showWait(boolean show) {
-        mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
-    }
-
-    //
-    // Common loging
-    //
-    // Schema
-    private static final String KEY_LATENCY = "latency";
-    private static final String KEY_CONFIDENCE = "confidence";
-    private static final String KEY_SAMPLE_RATE = "sample_rate";
-    private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
-    private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
-    private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
-    private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
-    private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
-    private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
-    private static final String KEY_TEST_MMAP = "supports_mmap";
-    private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
-    private static final String KEY_LEVEL = "level";
-    private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
-
-    @Override
-    public String getTestId() {
-        return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
-    }
-
-    //
-    // Subclasses should call this explicitly. SubClasses should call submit() after their logs
-    //
-    @Override
-    public void recordTestResults() {
-        if (mNativeAnalyzerThread == null) {
-            return; // no results to report
-        }
-
-        CtsVerifierReportLog reportLog = getReportLog();
-        reportLog.addValue(
-                KEY_LATENCY,
-                mMeanLatencyMillis,
-                ResultType.LOWER_BETTER,
-                ResultUnit.MS);
-
-        reportLog.addValue(
-                KEY_CONFIDENCE,
-                mMeanConfidence,
-                ResultType.HIGHER_BETTER,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_SAMPLE_RATE,
-                mNativeAnalyzerThread.getSampleRate(),
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_LOW_LATENCY,
-                mNativeAnalyzerThread.isLowLatencyStream(),
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_PERIPHERAL_ATTACHED,
-                mIsPeripheralAttached,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_IS_PRO_AUDIO,
-                mClaimsProAudio,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_PERIPHERAL,
-                mTestPeripheral,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_MMAP,
-                mSupportsMMAP,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.addValue(
-                KEY_TEST_MMAPEXCLUSIVE ,
-                mSupportsMMAPExclusive,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        if (mIsPeripheralAttached) {
-            reportLog.addValue(
-                    KEY_INPUT_PERIPHERAL_NAME,
-                    mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
-                    ResultType.NEUTRAL,
-                    ResultUnit.NONE);
-
-            reportLog.addValue(
-                    KEY_OUTPUT_PERIPHERAL_NAME,
-                    mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
-                    ResultType.NEUTRAL,
-                    ResultUnit.NONE);
-        }
-
-        int audioLevel = mAudioLevelSeekbar.getProgress();
-        reportLog.addValue(
-                KEY_LEVEL,
-                audioLevel,
-                ResultType.NEUTRAL,
-                ResultUnit.NONE);
-
-        reportLog.submit();
-    }
-
-    private void startAudioTest(Handler messageHandler) {
-        getPassButton().setEnabled(false);
-
-        mTestPhase = 0;
-        java.util.Arrays.fill(mLatencyMillis, 0.0);
-        java.util.Arrays.fill(mConfidence, 0.0);
-
-        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
-        if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.setMessageHandler(messageHandler);
-            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
-            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
-            startTestPhase();
-        } else {
-            Log.e(TAG, "Couldn't allocate native analyzer thread");
-            mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
-        }
-    }
-
-    private void startTestPhase() {
-        if (mNativeAnalyzerThread != null) {
-            mNativeAnalyzerThread.startTest();
-
-            // what is this for?
-            try {
-                Thread.sleep(200);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-    }
-
-    private void handleTestCompletion() {
-        mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
-        mMeanAbsoluteDeviation =
-                StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
-        mMeanConfidence = StatUtils.calculateMean(mConfidence);
-
-        boolean pass = isPeripheralValidForTest()
-                && mMeanConfidence >= CONFIDENCE_THRESHOLD
-                && mMeanLatencyMillis > EPSILON
-                && mMeanLatencyMillis < mMustLatency;
-
-        getPassButton().setEnabled(pass);
-
-        String result;
-        if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
-            result = String.format(
-                    "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
-                    mMeanConfidence, CONFIDENCE_THRESHOLD);
-        } else {
-            result = String.format(
-                    "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
-                            "Mean Absolute Deviation: %.2f\n" +
-                            " Confidence: %.2f\n" +
-                            " Low Latency Path: %s",
-                    (pass ? "PASS" : "FAIL"),
-                    mMeanLatencyMillis,
-                    mMustLatency,
-                    mMeanAbsoluteDeviation,
-                    mMeanConfidence,
-                    mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
-        }
-
-        // Make sure the test thread is finished. It should already be done.
-        if (mNativeAnalyzerThread != null) {
-            try {
-                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-        }
-        mResultText.setText(result);
-
-        recordTestResults();
-
-        showWait(false);
-        mTestButton.setEnabled(true);
-    }
-
-    private void handleTestPhaseCompletion() {
-        if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
-            mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
-            mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
-
-            String result = String.format(
-                    "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
-                    mTestPhase,
-                    mLatencyMillis[mTestPhase],
-                    mConfidence[mTestPhase]);
-
-            mResultText.setText(result);
-            try {
-                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
-                // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
-            } catch (InterruptedException e) {
-                e.printStackTrace();
-            }
-
-            mTestPhase++;
-            if (mTestPhase >= NUM_TEST_PHASES) {
-                handleTestCompletion();
-            } else {
-                startTestPhase();
-            }
-        }
-    }
-
-    /**
-     * handler for messages from audio thread
-     */
-    private Handler mMessageHandler = new Handler() {
-        public void handleMessage(Message msg) {
-            super.handleMessage(msg);
-            switch(msg.what) {
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
-                    Log.v(TAG,"got message native rec started!!");
-                    showWait(true);
-                    mResultText.setText(String.format("[phase: %d] - Test Running...",
-                            (mTestPhase + 1)));
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error opening streams.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
-                    Log.v(TAG,"got message native rec can't start!!");
-                    mResultText.setText("Test Error while recording.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
-                    mResultText.setText("Test FAILED due to errors.");
-                    handleTestCompletion();
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
-                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
-                    mResultText.setText(String.format("[phase: %d] - Analyzing ...",
-                            mTestPhase + 1));
-                    break;
-                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
-                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
-                    handleTestPhaseCompletion();
-                    break;
-                default:
-                    break;
-            }
-        }
-    };
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        setContentView(R.layout.audio_loopback_latency_activity);
-
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-        setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
-
-        mClaimsOutput = AudioSystemFlags.claimsOutput(this);
-        mClaimsInput = AudioSystemFlags.claimsInput(this);
-        mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
-
-        mYesString = getResources().getString(R.string.audio_general_yes);
-        mNoString = getResources().getString(R.string.audio_general_no);
-
-        // Pro Audio
-        ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
-                "" + (mClaimsProAudio ? mYesString : mNoString));
-
-        // MMAP
-        ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
-                "" + (mSupportsMMAP ? mYesString : mNoString));
-        ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
-                "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
-
-        // Low Latency
-        ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
-                "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
-
-        mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
-
-        mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
-        mTestButton.setOnClickListener(mBtnClickListener);
-
-        mAudioManager = (AudioManager)getSystemService(AUDIO_SERVICE);
-
-        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
-
-        connectLoopbackUI();
-
-        calculateLatencyThresholds();
-        displayLatencyThresholds();
-    }
-
-    private class OnBtnClickListener implements OnClickListener {
-        @Override
-        public void onClick(View v) {
-            switch (v.getId()) {
-                case R.id.audio_loopback_test_btn:
-                    Log.i(TAG, "audio loopback test");
-                    startAudioTest(mMessageHandler);
-                    break;
-            }
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
index 9490091..8ee3446 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioLoopbackLatencyActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2015 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,9 +16,679 @@
 
 package com.android.cts.verifier.audio;
 
+import android.app.AlertDialog;
+import android.media.AudioDeviceCallback;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.media.MediaRecorder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.audio.audiolib.AudioSystemFlags;
+import com.android.cts.verifier.audio.audiolib.StatUtils;
+import com.android.cts.verifier.audio.audiolib.AudioUtils;
+import com.android.cts.verifier.CtsVerifierReportLog;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import static com.android.cts.verifier.TestListActivity.sCurrentDisplayMode;
+import static com.android.cts.verifier.TestListAdapter.setTestNameSuffix;
+
 /**
- * Tests Audio Device roundtrip latency by using a loopback plug.
+ * CtsVerifier Audio Loopback Latency Test
  */
-public class AudioLoopbackLatencyActivity extends AudioLoopbackBaseActivity {
-    private static final String TAG = AudioLoopbackLatencyActivity.class.getSimpleName();
+public class AudioLoopbackLatencyActivity extends PassFailButtons.Activity {
+    private static final String TAG = "AudioLoopbackLatencyActivity";
+
+    // JNI load
+    static {
+        try {
+            System.loadLibrary("audioloopback_jni");
+        } catch (UnsatisfiedLinkError e) {
+            Log.e(TAG, "Error loading Audio Loopback JNI library");
+            Log.e(TAG, "e: " + e);
+            e.printStackTrace();
+        }
+
+        /* TODO: gracefully fail/notify if the library can't be loaded */
+    }
+    protected AudioManager mAudioManager;
+
+    // UI
+    TextView mInputDeviceTxt;
+    TextView mOutputDeviceTxt;
+
+    TextView mAudioLevelText;
+    SeekBar mAudioLevelSeekbar;
+
+    TextView mTestPathTxt;
+
+    TextView mResultText;
+    ProgressBar mProgressBar;
+    int mMaxLevel;
+
+    OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
+    protected Button mTestButton;
+
+    String mYesString;
+    String mNoString;
+
+    // These flags determine the maximum allowed latency
+    private boolean mClaimsProAudio;
+    private boolean mClaimsOutput;
+    private boolean mClaimsInput;
+
+    // Useful info
+    private boolean mSupportsMMAP = AudioUtils.isMMapSupported();
+    private boolean mSupportsMMAPExclusive = AudioUtils.isMMapExclusiveSupported();
+
+    // Peripheral(s)
+    boolean mIsPeripheralAttached;  // CDD ProAudio section C-1-3
+    AudioDeviceInfo mOutputDevInfo;
+    AudioDeviceInfo mInputDevInfo;
+
+    protected static final int TESTPERIPHERAL_INVALID       = -1;
+    protected static final int TESTPERIPHERAL_NONE          = 0;
+    protected static final int TESTPERIPHERAL_ANALOG_JACK   = 1;
+    protected static final int TESTPERIPHERAL_USB           = 2;
+    protected static final int TESTPERIPHERAL_DEVICE        = 3; // device speaker + mic
+    protected int mTestPeripheral = TESTPERIPHERAL_NONE;
+
+    // Loopback Logic
+    NativeAnalyzerThread mNativeAnalyzerThread = null;
+
+    protected static final int NUM_TEST_PHASES = 5;
+    protected int mTestPhase = 0;
+
+    protected double[] mLatencyMillis = new double[NUM_TEST_PHASES];
+    protected double[] mConfidence = new double[NUM_TEST_PHASES];
+
+    protected double mMeanLatencyMillis;
+    protected double mMeanAbsoluteDeviation;
+    protected double mMeanConfidence;
+
+    protected static final double CONFIDENCE_THRESHOLD = 0.6;
+    // impossibly low latencies (indicating something in the test went wrong).
+    protected static final float EPSILON = 1.0f;
+    protected static final double PROAUDIO_RECOMMENDED_LATENCY_MS = 20.0;
+    protected static final double PROAUDIO_RECOMMENDED_USB_LATENCY_MS = 25.0;
+    protected static final double PROAUDIO_MUST_LATENCY_MS = 20.0;
+    protected static final double BASIC_RECOMMENDED_LATENCY_MS = 50.0;
+    protected static final double BASIC_MUST_LATENCY_MS = 800.0;
+    protected double mMustLatency;
+    protected double mRecommendedLatency;
+
+    // The audio stream callback threads should stop and close
+    // in less than a few hundred msec. This is a generous timeout value.
+    private static final int STOP_TEST_TIMEOUT_MSEC = 2 * 1000;
+
+    //
+    // Common UI Handling
+    //
+    private void connectLoopbackUI() {
+        // Connected Device
+        mInputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackInputLbl));
+        mOutputDeviceTxt = ((TextView)findViewById(R.id.audioLoopbackOutputLbl));
+
+        mAudioLevelText = (TextView)findViewById(R.id.audio_loopback_level_text);
+        mAudioLevelSeekbar = (SeekBar)findViewById(R.id.audio_loopback_level_seekbar);
+        mMaxLevel = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setMax(mMaxLevel);
+        mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int)(0.7 * mMaxLevel), 0);
+        refreshLevel();
+
+        mAudioLevelSeekbar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
+            @Override
+            public void onStopTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onStartTrackingTouch(SeekBar seekBar) {}
+
+            @Override
+            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+                mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC,
+                        progress, 0);
+                Log.i(TAG,"Level set to: " + progress);
+                refreshLevel();
+            }
+        });
+
+        mResultText = (TextView)findViewById(R.id.audio_loopback_results_text);
+        mProgressBar = (ProgressBar)findViewById(R.id.audio_loopback_progress_bar);
+        showWait(false);
+    }
+
+    //
+    // Peripheral Connection Logic
+    //
+    protected void scanPeripheralList(AudioDeviceInfo[] devices) {
+        // CDD Section C-1-3: USB port, host-mode support
+
+        // Can't just use the first record because then we will only get
+        // Source OR sink, not both even on devices that are both.
+        mOutputDevInfo = null;
+        mInputDevInfo = null;
+
+        // Any valid peripherals
+        // Do we leave in the Headset test to support a USB-Dongle?
+        for (AudioDeviceInfo devInfo : devices) {
+            if (devInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE || // USB Peripheral
+                    devInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET || // USB dongle+LBPlug
+                    devInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET || // Loopback Plug?
+                    devInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) { // Aux-cable loopback?
+                if (devInfo.isSink()) {
+                    mOutputDevInfo = devInfo;
+                }
+                if (devInfo.isSource()) {
+                    mInputDevInfo = devInfo;
+                }
+            }  else {
+                handleDeviceConnection(devInfo);
+            }
+        }
+
+        // need BOTH input and output to test
+        mIsPeripheralAttached = mOutputDevInfo != null && mInputDevInfo != null;
+        calculateTestPeripheral();
+        showConnectedAudioPeripheral();
+        calculateLatencyThresholds();
+        displayLatencyThresholds();
+    }
+
+    protected void handleDeviceConnection(AudioDeviceInfo deviceInfo) {
+        // NOP
+    }
+
+    private class ConnectListener extends AudioDeviceCallback {
+        /*package*/ ConnectListener() {}
+
+        //
+        // AudioDevicesManager.OnDeviceConnectionListener
+        //
+        @Override
+        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+
+        @Override
+        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
+            scanPeripheralList(mAudioManager.getDevices(AudioManager.GET_DEVICES_ALL));
+        }
+    }
+
+    private void calculateTestPeripheral() {
+        if (!mIsPeripheralAttached) {
+            if ((mOutputDevInfo != null && mInputDevInfo == null)
+                || (mOutputDevInfo == null && mInputDevInfo != null)) {
+                mTestPeripheral = TESTPERIPHERAL_INVALID;
+            } else {
+                mTestPeripheral = TESTPERIPHERAL_DEVICE;
+            }
+        } else if (!areIODevicesOnePeripheral()) {
+            mTestPeripheral = TESTPERIPHERAL_INVALID;
+        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_DEVICE ||
+                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_USB_HEADSET) {
+            mTestPeripheral = TESTPERIPHERAL_USB;
+        } else if (mInputDevInfo.getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
+                mInputDevInfo.getType() == AudioDeviceInfo.TYPE_AUX_LINE) {
+            mTestPeripheral = TESTPERIPHERAL_ANALOG_JACK;
+        } else {
+            // Huh?
+            Log.e(TAG, "No valid peripheral found!?");
+            mTestPeripheral = TESTPERIPHERAL_NONE;
+        }
+    }
+
+    private boolean isPeripheralValidForTest() {
+        return mTestPeripheral == TESTPERIPHERAL_ANALOG_JACK
+                    || mTestPeripheral == TESTPERIPHERAL_USB;
+    }
+
+    private void showConnectedAudioPeripheral() {
+        mInputDeviceTxt.setText(
+                mInputDevInfo != null ? mInputDevInfo.getProductName().toString()
+                        : "Not connected");
+        mOutputDeviceTxt.setText(
+                mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString()
+                        : "Not connected");
+
+        String pathName;
+        switch (mTestPeripheral) {
+            case TESTPERIPHERAL_INVALID:
+                pathName = "Invalid Test Peripheral";
+                break;
+
+            case TESTPERIPHERAL_ANALOG_JACK:
+                pathName = "Headset Jack";
+                break;
+
+            case TESTPERIPHERAL_USB:
+                pathName = "USB";
+                break;
+
+            case TESTPERIPHERAL_DEVICE:
+                pathName = "Device Speaker + Microphone";
+                break;
+
+            case TESTPERIPHERAL_NONE:
+            default:
+                pathName = "Error. Unknown Test Path";
+                break;
+        }
+        mTestPathTxt.setText(pathName);
+        mTestButton.setEnabled(
+                mTestPeripheral != TESTPERIPHERAL_INVALID && mTestPeripheral != TESTPERIPHERAL_NONE);
+
+    }
+
+    private boolean areIODevicesOnePeripheral() {
+        if (mOutputDevInfo == null || mInputDevInfo == null) {
+            return false;
+        }
+
+        return mOutputDevInfo.getProductName().toString().equals(
+                mInputDevInfo.getProductName().toString());
+    }
+
+    private void calculateLatencyThresholds() {
+        switch (mTestPeripheral) {
+            case TESTPERIPHERAL_ANALOG_JACK:
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency =  mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_USB:
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_USB_LATENCY_MS : BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_DEVICE:
+                // This isn't a valid case so we won't pass it, but it can be run
+                mRecommendedLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS : BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = mClaimsProAudio
+                        ? PROAUDIO_RECOMMENDED_LATENCY_MS :BASIC_MUST_LATENCY_MS;
+                break;
+
+            case TESTPERIPHERAL_NONE:
+            default:
+                mRecommendedLatency = BASIC_RECOMMENDED_LATENCY_MS;
+                mMustLatency = BASIC_MUST_LATENCY_MS;
+                break;
+        }
+    }
+
+    private void displayLatencyThresholds() {
+        if (isPeripheralValidForTest()) {
+            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText("" + mMustLatency);
+            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(
+                    "" + mRecommendedLatency);
+        } else {
+            String naStr = getResources().getString(R.string.audio_proaudio_NA);
+            ((TextView) findViewById(R.id.audio_loopback_must_latency)).setText(naStr);
+            ((TextView) findViewById(R.id.audio_loopback_recommended_latency)).setText(naStr);
+        }
+    }
+
+    /**
+     * refresh Audio Level seekbar and text
+     */
+    private void refreshLevel() {
+        int currentLevel = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
+        mAudioLevelSeekbar.setProgress(currentLevel);
+
+        String levelText = String.format("%s: %d/%d",
+                getResources().getString(R.string.audio_loopback_level_text),
+                currentLevel, mMaxLevel);
+        mAudioLevelText.setText(levelText);
+    }
+
+    //
+    // show active progress bar
+    //
+    protected void showWait(boolean show) {
+        mProgressBar.setVisibility(show ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    //
+    // Common logging
+    //
+    // Schema
+    private static final String KEY_LATENCY = "latency";
+    private static final String KEY_CONFIDENCE = "confidence";
+    private static final String KEY_SAMPLE_RATE = "sample_rate";
+    private static final String KEY_IS_PRO_AUDIO = "is_pro_audio";
+    private static final String KEY_IS_LOW_LATENCY = "is_low_latency";
+    private static final String KEY_IS_PERIPHERAL_ATTACHED = "is_peripheral_attached";
+    private static final String KEY_INPUT_PERIPHERAL_NAME = "input_peripheral";
+    private static final String KEY_OUTPUT_PERIPHERAL_NAME = "output_peripheral";
+    private static final String KEY_TEST_PERIPHERAL = "test_peripheral";
+    private static final String KEY_TEST_MMAP = "supports_mmap";
+    private static final String KEY_TEST_MMAPEXCLUSIVE = "supports_mmap_exclusive";
+    private static final String KEY_LEVEL = "level";
+    private static final String KEY_BUFFER_SIZE = "buffer_size_in_frames";
+
+    @Override
+    public String getTestId() {
+        return setTestNameSuffix(sCurrentDisplayMode, getClass().getName());
+    }
+
+    @Override
+    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+    @Override
+    public final String getReportSectionName() {
+        return setTestNameSuffix(sCurrentDisplayMode, "audio_loopback_latency_activity");
+    }
+
+    //
+    // Subclasses should call this explicitly. SubClasses should call submit() after their logs
+    //
+    @Override
+    public void recordTestResults() {
+        if (mNativeAnalyzerThread == null) {
+            return; // no results to report
+        }
+
+        CtsVerifierReportLog reportLog = getReportLog();
+        reportLog.addValue(
+                KEY_LATENCY,
+                mMeanLatencyMillis,
+                ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+
+        reportLog.addValue(
+                KEY_CONFIDENCE,
+                mMeanConfidence,
+                ResultType.HIGHER_BETTER,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_SAMPLE_RATE,
+                mNativeAnalyzerThread.getSampleRate(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_LOW_LATENCY,
+                mNativeAnalyzerThread.isLowLatencyStream(),
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_PERIPHERAL_ATTACHED,
+                mIsPeripheralAttached,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_IS_PRO_AUDIO,
+                mClaimsProAudio,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_PERIPHERAL,
+                mTestPeripheral,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_MMAP,
+                mSupportsMMAP,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.addValue(
+                KEY_TEST_MMAPEXCLUSIVE ,
+                mSupportsMMAPExclusive,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        if (mIsPeripheralAttached) {
+            reportLog.addValue(
+                    KEY_INPUT_PERIPHERAL_NAME,
+                    mInputDevInfo != null ? mInputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+
+            reportLog.addValue(
+                    KEY_OUTPUT_PERIPHERAL_NAME,
+                    mOutputDevInfo != null ? mOutputDevInfo.getProductName().toString() : "None",
+                    ResultType.NEUTRAL,
+                    ResultUnit.NONE);
+        }
+
+        int audioLevel = mAudioLevelSeekbar.getProgress();
+        reportLog.addValue(
+                KEY_LEVEL,
+                audioLevel,
+                ResultType.NEUTRAL,
+                ResultUnit.NONE);
+
+        reportLog.submit();
+    }
+
+    private void startAudioTest(Handler messageHandler) {
+        getPassButton().setEnabled(false);
+
+        mTestPhase = 0;
+        java.util.Arrays.fill(mLatencyMillis, 0.0);
+        java.util.Arrays.fill(mConfidence, 0.0);
+
+        mNativeAnalyzerThread = new NativeAnalyzerThread(this);
+        if (mNativeAnalyzerThread != null) {
+            mNativeAnalyzerThread.setMessageHandler(messageHandler);
+            // This value matches AAUDIO_INPUT_PRESET_VOICE_RECOGNITION
+            mNativeAnalyzerThread.setInputPreset(MediaRecorder.AudioSource.VOICE_RECOGNITION);
+            startTestPhase();
+        } else {
+            Log.e(TAG, "Couldn't allocate native analyzer thread");
+            mResultText.setText(getResources().getString(R.string.audio_loopback_failure));
+        }
+    }
+
+    private void startTestPhase() {
+        if (mNativeAnalyzerThread != null) {
+            mNativeAnalyzerThread.startTest();
+
+            // what is this for?
+            try {
+                Thread.sleep(200);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private void handleTestCompletion() {
+        mMeanLatencyMillis = StatUtils.calculateMean(mLatencyMillis);
+        mMeanAbsoluteDeviation =
+                StatUtils.calculateMeanAbsoluteDeviation(mMeanLatencyMillis, mLatencyMillis);
+        mMeanConfidence = StatUtils.calculateMean(mConfidence);
+
+        boolean pass = isPeripheralValidForTest()
+                && mMeanConfidence >= CONFIDENCE_THRESHOLD
+                && mMeanLatencyMillis > EPSILON
+                && mMeanLatencyMillis < mMustLatency;
+
+        getPassButton().setEnabled(pass);
+
+        String result;
+        if (mMeanConfidence < CONFIDENCE_THRESHOLD) {
+            result = String.format(
+                    "Test Finished\nInsufficient Confidence (%.2f < %.2f). No Results.",
+                    mMeanConfidence, CONFIDENCE_THRESHOLD);
+        } else {
+            result = String.format(
+                    "Test Finished - %s\nMean Latency:%.2f ms (required:%.2f)\n" +
+                            "Mean Absolute Deviation: %.2f\n" +
+                            " Confidence: %.2f\n" +
+                            " Low Latency Path: %s",
+                    (pass ? "PASS" : "FAIL"),
+                    mMeanLatencyMillis,
+                    mMustLatency,
+                    mMeanAbsoluteDeviation,
+                    mMeanConfidence,
+                    mNativeAnalyzerThread.isLowLatencyStream() ? mYesString : mNoString);
+        }
+
+        // Make sure the test thread is finished. It should already be done.
+        if (mNativeAnalyzerThread != null) {
+            try {
+                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        mResultText.setText(result);
+
+        recordTestResults();
+
+        showWait(false);
+        mTestButton.setEnabled(true);
+    }
+
+    private void handleTestPhaseCompletion() {
+        if (mNativeAnalyzerThread != null && mTestPhase < NUM_TEST_PHASES) {
+            mLatencyMillis[mTestPhase] = mNativeAnalyzerThread.getLatencyMillis();
+            mConfidence[mTestPhase] = mNativeAnalyzerThread.getConfidence();
+
+            String result = String.format(
+                    "Test %d Finished\nLatency: %.2f ms\nConfidence: %.2f\n",
+                    mTestPhase,
+                    mLatencyMillis[mTestPhase],
+                    mConfidence[mTestPhase]);
+
+            mResultText.setText(result);
+            try {
+                mNativeAnalyzerThread.stopTest(STOP_TEST_TIMEOUT_MSEC);
+                // Thread.sleep(/*STOP_TEST_TIMEOUT_MSEC*/500);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+
+            mTestPhase++;
+            if (mTestPhase >= NUM_TEST_PHASES) {
+                handleTestCompletion();
+            } else {
+                startTestPhase();
+            }
+        }
+    }
+
+    /**
+     * handler for messages from audio thread
+     */
+    private Handler mMessageHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            super.handleMessage(msg);
+            switch(msg.what) {
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_STARTED:
+                    Log.v(TAG,"got message native rec started!!");
+                    showWait(true);
+                    mResultText.setText(String.format("[phase: %d] - Test Running...",
+                            (mTestPhase + 1)));
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_OPEN_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error opening streams.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_ERROR:
+                    Log.v(TAG,"got message native rec can't start!!");
+                    mResultText.setText("Test Error while recording.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE_ERRORS:
+                    mResultText.setText("Test FAILED due to errors.");
+                    handleTestCompletion();
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING:
+                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_ANALYZING");
+                    mResultText.setText(String.format("[phase: %d] - Analyzing ...",
+                            mTestPhase + 1));
+                    break;
+                case NativeAnalyzerThread.NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE:
+                    Log.i(TAG, "NATIVE_AUDIO_THREAD_MESSAGE_REC_COMPLETE");
+                    handleTestPhaseCompletion();
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.audio_loopback_latency_activity);
+
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+        setInfoResources(R.string.audio_loopback_latency_test, R.string.audio_loopback_info, -1);
+
+        mClaimsOutput = AudioSystemFlags.claimsOutput(this);
+        mClaimsInput = AudioSystemFlags.claimsInput(this);
+        mClaimsProAudio = AudioSystemFlags.claimsProAudio(this);
+
+        mYesString = getResources().getString(R.string.audio_general_yes);
+        mNoString = getResources().getString(R.string.audio_general_no);
+
+        // Pro Audio
+        ((TextView)findViewById(R.id.audio_loopback_pro_audio)).setText(
+                "" + (mClaimsProAudio ? mYesString : mNoString));
+
+        // MMAP
+        ((TextView)findViewById(R.id.audio_loopback_mmap)).setText(
+                "" + (mSupportsMMAP ? mYesString : mNoString));
+        ((TextView)findViewById(R.id.audio_loopback_mmap_exclusive)).setText(
+                "" + (mSupportsMMAPExclusive ? mYesString : mNoString));
+
+        // Low Latency
+        ((TextView)findViewById(R.id.audio_loopback_low_latency)).setText(
+                "" + (AudioSystemFlags.claimsLowLatencyAudio(this) ? mYesString : mNoString));
+
+        mTestPathTxt = ((TextView)findViewById(R.id.audio_loopback_testpath));
+
+        mTestButton = (Button)findViewById(R.id.audio_loopback_test_btn);
+        mTestButton.setOnClickListener(mBtnClickListener);
+
+        mAudioManager = getSystemService(AudioManager.class);
+
+        mAudioManager.registerAudioDeviceCallback(new ConnectListener(), new Handler());
+
+        connectLoopbackUI();
+
+        calculateLatencyThresholds();
+        displayLatencyThresholds();
+    }
+
+    private class OnBtnClickListener implements OnClickListener {
+        @Override
+        public void onClick(View v) {
+            switch (v.getId()) {
+                case R.id.audio_loopback_test_btn:
+                    Log.i(TAG, "audio loopback test");
+                    startAudioTest(mMessageHandler);
+                    break;
+            }
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
index 0e4f6da..29b1234 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputDeviceNotificationsActivity.java
@@ -16,22 +16,16 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.R;
-
 import android.content.Context;
-
 import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
-
 import android.os.Bundle;
-
 import android.view.View;
-import android.view.View.OnClickListener;
-
-import android.widget.Button;
 import android.widget.TextView;
 
+import com.android.cts.verifier.R;
+
 /**
  * Tests Audio Device Connection events for output devices by prompting the user to
  * insert/remove a wired headset and noting the presence (or absence) of notifications.
@@ -41,13 +35,24 @@
 
     TextView mConnectView;
     TextView mDisconnectView;
-    Button mClearMsgsBtn;
+    TextView mInfoView;
+
+    boolean mHandledInitialAddedMessage = false;
+    boolean mConnectReceived = false;
+    boolean mDisconnectReceived = false;
 
     private class TestAudioDeviceCallback extends AudioDeviceCallback {
         public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
+            // we will get this message when we setup the handler, so ignore the first one.
+            if (!mHandledInitialAddedMessage) {
+                mHandledInitialAddedMessage = true;
+                return;
+            }
             if (addedDevices.length != 0) {
                 mConnectView.setText(
                     mContext.getResources().getString(R.string.audio_dev_notification_connectMsg));
+                mConnectReceived = true;
+                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
             }
         }
 
@@ -56,35 +61,25 @@
                 mDisconnectView.setText(
                     mContext.getResources().getString(
                         R.string.audio_dev_notification_disconnectMsg));
+                mDisconnectReceived = true;
+                getPassButton().setEnabled(mConnectReceived && mDisconnectReceived);
             }
         }
     }
 
     @Override
-    protected void enableTestButtons(boolean enabled) {
-        // Nothing to do.
-    }
-
-    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.audio_dev_notify);
 
         mContext = this;
 
-        mConnectView = (TextView)findViewById(R.id.audio_dev_notification_connect_msg);
-        mDisconnectView = (TextView)findViewById(R.id.audio_dev_notification_disconnect_msg);
+        mConnectView = (TextView) findViewById(R.id.audio_dev_notification_connect_msg);
+        mDisconnectView = (TextView) findViewById(R.id.audio_dev_notification_disconnect_msg);
 
-        ((TextView)findViewById(R.id.info_text)).setText(mContext.getResources().getString(
-                R.string.audio_out_devices_notification_instructions));
-
-        mClearMsgsBtn = (Button)findViewById(R.id.audio_dev_notification_connect_clearmsgs_btn);
-        mClearMsgsBtn.setOnClickListener(new View.OnClickListener() {
-            public void onClick(View v) {
-                mConnectView.setText("");
-                mDisconnectView.setText("");
-            }
-        });
+        mInfoView = (TextView) findViewById(R.id.info_text);
+        mInfoView.setText(mContext.getResources().getString(
+                R.string.audio_devices_notification_instructions));
 
         AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
         audioManager.registerAudioDeviceCallback(new TestAudioDeviceCallback(), null);
@@ -94,4 +89,9 @@
 
         setPassFailButtonClickListeners();
     }
+
+    @Override
+    protected void enableTestButtons(boolean enabled) {
+        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
index 62749c1..b5d64ca 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioOutputRoutingNotificationsActivity.java
@@ -16,27 +16,19 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.R;
-
 import android.content.Context;
-
-import android.media.AudioDeviceCallback;
 import android.media.AudioDeviceInfo;
-import android.media.AudioManager;
 import android.media.AudioTrack;
-
 import android.os.Bundle;
 import android.os.Handler;
-
 import android.util.Log;
-
 import android.view.View;
 import android.view.View.OnClickListener;
-
 import android.widget.Button;
 import android.widget.TextView;
 
-import org.hyphonate.megaaudio.player.AudioSource;
+import com.android.cts.verifier.R;
+
 import org.hyphonate.megaaudio.player.AudioSourceProvider;
 import org.hyphonate.megaaudio.player.JavaPlayer;
 import org.hyphonate.megaaudio.player.PlayerBuilder;
@@ -55,13 +47,21 @@
 
     Button playBtn;
     Button stopBtn;
+    TextView mInfoView;
+
+    int mNumRoutingNotifications;
 
     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    int mNumTrackNotifications = 0;
+    // ignore messages sent as a consequence of starting the player
+    private static final int NUM_IGNORE_MESSAGES = 1;
 
     // Mega Player
-    JavaPlayer mAudioPlayer;
+    private JavaPlayer mAudioPlayer;
+    private AudioTrackRoutingChangeListener mRoutingChangeListener;
+    private boolean mIsPlaying;
+
+    private boolean mInitialRoutingMessageHandled;
 
     private class OnBtnClickListener implements OnClickListener {
         @Override
@@ -71,24 +71,53 @@
             }
             switch (v.getId()) {
                 case R.id.audio_routingnotification_playBtn:
-                {
-                    mAudioPlayer.startStream();
-                    AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
-                    audioTrack.addOnRoutingChangedListener(
-                            new AudioTrackRoutingChangeListener(), new Handler());
-                }
+                    startPlayback();
                     break;
 
                 case R.id.audio_routingnotification_playStopBtn:
-                    mAudioPlayer.stopStream();
+                    stopPlayback();
                     break;
             }
         }
     }
 
+    private void startPlayback() {
+        if (!mIsPlaying) {
+            mNumRoutingNotifications = 0;
+
+            mAudioPlayer.startStream();
+
+            AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+            audioTrack.addOnRoutingChangedListener(mRoutingChangeListener,
+                    new Handler());
+
+            mIsPlaying = true;
+
+            enablePlayButtons(!mIsPlaying, mIsPlaying);
+        }
+    }
+
+    private void stopPlayback() {
+        if (mIsPlaying) {
+            mAudioPlayer.stopStream();
+
+            AudioTrack audioTrack = mAudioPlayer.getAudioTrack();
+            audioTrack.removeOnRoutingChangedListener(mRoutingChangeListener);
+
+            mIsPlaying = false;
+
+            enablePlayButtons(!mIsPlaying, mIsPlaying);
+        }
+    }
+
     private class AudioTrackRoutingChangeListener implements AudioTrack.OnRoutingChangedListener {
         public void onRoutingChanged(AudioTrack audioTrack) {
-            mNumTrackNotifications++;
+            // Starting playback triggers a messages, so ignore the first one.
+            mNumRoutingNotifications++;
+            if (mNumRoutingNotifications <= NUM_IGNORE_MESSAGES) {
+                return;
+            }
+
             TextView textView =
                 (TextView)findViewById(R.id.audio_routingnotification_audioTrack_change);
             String msg = mContext.getResources().getString(
@@ -98,14 +127,21 @@
             int deviceType = routedDevice != null ? routedDevice.getType() : -1;
             textView.setText(msg + " - " +
                              deviceName + " [0x" + Integer.toHexString(deviceType) + "]" +
-                             " - " + mNumTrackNotifications);
+                             " - " + mNumRoutingNotifications);
+            getPassButton().setEnabled(true);
         }
     }
 
     @Override
     protected void enableTestButtons(boolean enabled) {
-        playBtn.setEnabled(enabled);
-        stopBtn.setEnabled(enabled);
+        enablePlayButtons(!mIsPlaying, mIsPlaying);
+
+        mInfoView.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
+    }
+
+    private void enablePlayButtons(boolean enablePlay, boolean enableStop) {
+        playBtn.setEnabled(enablePlay);
+        stopBtn.setEnabled(enableStop);
     }
 
     @Override
@@ -115,11 +151,15 @@
 
         mContext = this;
 
-        playBtn = (Button)findViewById(R.id.audio_routingnotification_playBtn);
+        playBtn = (Button) findViewById(R.id.audio_routingnotification_playBtn);
         playBtn.setOnClickListener(mBtnClickListener);
-        stopBtn = (Button)findViewById(R.id.audio_routingnotification_playStopBtn);
+        stopBtn = (Button) findViewById(R.id.audio_routingnotification_playStopBtn);
         stopBtn.setOnClickListener(mBtnClickListener);
 
+        enablePlayButtons(false, false);
+
+        mInfoView = (TextView) findViewById(R.id.info_text);
+
         // Setup Player
         //
         // Allocate the source provider for the sort of signal we want to play
@@ -139,17 +179,16 @@
             Log.e(TAG, "Failed MegaPlayer build.");
         }
 
+        mRoutingChangeListener = new AudioTrackRoutingChangeListener();
+
         // "Honor System" buttons
         super.setup();
-
         setPassFailButtonClickListeners();
     }
 
     @Override
     public void onBackPressed () {
-        if (mAudioPlayer != null) {
-            mAudioPlayer.stopStream();
-        }
+        stopPlayback();
         super.onBackPressed();
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
index 2e308f2..97878f4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/AudioWiredDeviceBaseActivity.java
@@ -16,31 +16,24 @@
 
 package com.android.cts.verifier.audio;
 
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import com.android.compatibility.common.util.ReportLog;
-import com.android.compatibility.common.util.ResultType;
-import com.android.compatibility.common.util.ResultUnit;
-
-import android.content.Context;
-
-import android.os.Bundle;
-import android.os.Handler;
-
 import android.util.Log;
-
 import android.view.View;
 import android.view.View.OnClickListener;
-
 import android.widget.Button;
 
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
 abstract class AudioWiredDeviceBaseActivity extends PassFailButtons.Activity {
     private static final String TAG = AudioWiredDeviceBaseActivity.class.getSimpleName();
 
     private OnBtnClickListener mBtnClickListener = new OnBtnClickListener();
 
-    abstract protected void enableTestButtons(boolean enabled);
+    protected void enableTestButtons(boolean enabled) {
+        // NOP by default
+    }
 
     private void recordWiredPortFound(boolean found) {
         getReportLog().addValue(
@@ -56,6 +49,8 @@
         ((Button)findViewById(R.id.audio_wired_yes)).setOnClickListener(mBtnClickListener);
 
         enableTestButtons(false);
+
+        getPassButton().setEnabled(false);
     }
 
     private class OnBtnClickListener implements OnClickListener {
@@ -66,12 +61,14 @@
                     Log.i(TAG, "User denies wired device existence");
                     enableTestButtons(false);
                     recordWiredPortFound(false);
+                    getPassButton().setEnabled(true);
                     break;
 
                 case R.id.audio_wired_yes:
                     Log.i(TAG, "User confirms wired device existence");
                     enableTestButtons(true);
                     recordWiredPortFound(true);
+                    getPassButton().setEnabled(false);
                     break;
             }
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
index d32749f..9804cd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/ProAudioActivity.java
@@ -67,6 +67,7 @@
     private static final String INFO_DIALOG_MESSAGE_ID = "infoDialogMessageId";
 
     // ReportLog Schema
+    private static final String SECTION_PRO_AUDIO_ACTIVITY = "pro_audio_activity";
     private static final String KEY_CLAIMS_PRO = "claims_pro_audio";
     private static final String KEY_CLAIMS_LOW_LATENCY = "claims_low_latency_audio";
     private static final String KEY_CLAIMS_MIDI = "claims_midi";
@@ -232,6 +233,14 @@
     // PassFailButtons Overrides
     //
     @Override
+    public String getReportFileName() { return PassFailButtons.AUDIO_TESTS_REPORT_LOG_NAME; }
+
+    @Override
+    public final String getReportSectionName() {
+        return setTestNameSuffix(sCurrentDisplayMode, SECTION_PRO_AUDIO_ACTIVITY);
+    }
+
+    @Override
     public void recordTestResults() {
 
         CtsVerifierReportLog reportLog = getReportLog();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
index 6c8c501..406a305 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/fov/PhotoCaptureActivity.java
@@ -23,6 +23,7 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Color;
+import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.Camera.PictureCallback;
 import android.hardware.Camera.ShutterCallback;
@@ -34,8 +35,7 @@
 import android.os.PowerManager.WakeLock;
 import android.util.Log;
 import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
+import android.view.TextureView;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AdapterView;
@@ -59,22 +59,24 @@
  * An activity for showing the camera preview and taking a picture.
  */
 public class PhotoCaptureActivity extends Activity
-        implements PictureCallback, SurfaceHolder.Callback {
+        implements PictureCallback, TextureView.SurfaceTextureListener {
     private static final String TAG = PhotoCaptureActivity.class.getSimpleName();
     private static final int FOV_REQUEST_CODE = 1006;
     private static final String PICTURE_FILENAME = "photo.jpg";
     private static float mReportedFovDegrees = 0;
     private float mReportedFovPrePictureTaken = -1;
 
-    private SurfaceView mPreview;
-    private SurfaceHolder mSurfaceHolder;
+    private TextureView mPreviewView;
+    private SurfaceTexture mPreviewTexture;
+    private int mPreviewTexWidth;
+    private int mPreviewTexHeight;
+
     private Spinner mResolutionSpinner;
     private List<SelectableResolution> mSupportedResolutions;
     private ArrayAdapter<SelectableResolution> mAdapter;
 
     private SelectableResolution mSelectedResolution;
     private Camera mCamera;
-    private Size mSurfaceSize;
     private boolean mCameraInitialized = false;
     private boolean mPreviewActive = false;
     private boolean mTakingPicture = false;
@@ -114,12 +116,8 @@
             }
         }
 
-        mPreview = (SurfaceView) findViewById(R.id.camera_fov_camera_preview);
-        mSurfaceHolder = mPreview.getHolder();
-        mSurfaceHolder.addCallback(this);
-
-        // This is required for older versions of Android hardware.
-        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
+        mPreviewView = (TextureView) findViewById(R.id.camera_fov_camera_preview);
+        mPreviewView.setSurfaceTextureListener(this);
 
         TextView textView = (TextView) findViewById(R.id.camera_fov_tap_to_take_photo);
         textView.setTextColor(Color.WHITE);
@@ -365,20 +363,27 @@
     }
 
     @Override
-    public void surfaceChanged(
-            SurfaceHolder holder, int format, int width, int height) {
-        mSurfaceSize = new Size(width, height);
+    public void onSurfaceTextureAvailable(SurfaceTexture surface,
+            int width, int height) {
+        mPreviewTexture = surface;
+        mPreviewTexWidth = width;
+        mPreviewTexHeight = height;
         initializeCamera();
     }
 
     @Override
-    public void surfaceCreated(SurfaceHolder holder) {
-        // Nothing to do.
+    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        // Ignored, Camera does all the work for us
     }
 
     @Override
-    public void surfaceDestroyed(SurfaceHolder holder) {
-        // Nothing to do.
+    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        return true;
+    }
+
+    @Override
+    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        // Invoked every time there's a new Camera preview frame
     }
 
     private void showNextDialogToChoosePreviewSize() {
@@ -432,14 +437,14 @@
     }
 
     private void initializeCamera(boolean startPreviewAfterInit) {
-        if (mCamera == null || mSurfaceHolder.getSurface() == null) {
+        if (mCamera == null || mPreviewTexture == null) {
             return;
         }
 
         try {
-            mCamera.setPreviewDisplay(mSurfaceHolder);
+            mCamera.setPreviewTexture(mPreviewTexture);
         } catch (Throwable t) {
-            Log.e(TAG, "Could not set preview display", t);
+            Log.e(TAG, "Could not set preview texture", t);
             Toast.makeText(this, t.getMessage(), Toast.LENGTH_LONG).show();
             return;
         }
@@ -452,13 +457,13 @@
         Size selectedPreviewSize = null;
         if (mPreviewSizes != null) {
             selectedPreviewSize = mPreviewSizes[mSelectedResolution.cameraId];
-        } else if (mSurfaceSize != null) {
+        } else {
             if (mPreviewOrientation == 0 || mPreviewOrientation == 180) {
                 selectedPreviewSize = getBestPreviewSize(
-                        mSurfaceSize.width, mSurfaceSize.height, params);
+                        mPreviewTexWidth, mPreviewTexHeight, params);
             } else {
                 selectedPreviewSize = getBestPreviewSize(
-                        mSurfaceSize.height, mSurfaceSize.width, params);
+                        mPreviewTexHeight, mPreviewTexWidth, params);
             }
         }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
index 7eae01c..924912f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/intents/CameraIntentsActivity.java
@@ -15,8 +15,11 @@
  */
 package com.android.cts.verifier.camera.intents;
 
+import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
+
+import android.Manifest;
 import android.app.job.JobInfo;
-import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -24,7 +27,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
-import android.content.pm.PermissionInfo;
 import android.hardware.Camera;
 import android.media.ExifInterface;
 import android.media.MediaMetadataRetriever;
@@ -39,24 +41,20 @@
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.TextView;
-import androidx.core.content.FileProvider;
-import android.Manifest;
+import android.widget.Toast;
 
-import com.android.cts.verifier.camera.intents.CameraContentJobService;
+import androidx.core.content.FileProvider;
+
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.TestResult;
-import android.widget.Toast;
-
-import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
-import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
-import java.util.TreeSet;
-import java.util.Date;
 import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TreeSet;
 
 /**
  * Tests for manual verification of uri trigger and camera intents being fired.
@@ -518,7 +516,11 @@
               mediaRetriever.extractMetadata(METADATA_KEY_HAS_VIDEO) +
               " METADATA_KEY_LOCATION: " +
               mediaRetriever.extractMetadata(METADATA_KEY_LOCATION));
-        mediaRetriever.release();
+        try {
+            mediaRetriever.release();
+        } catch (IOException e) {
+            // We ignore errors occurred while releasing the MediaMetadataRetriever.
+        }
         /* successful, unless we get the URI trigger back at some point later on. */
         mActionSuccess = true;
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 63d9687..1651ec5 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -150,8 +150,6 @@
 
     // Performance class R version number
     private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
-    // Performance class S version number
-    private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
 
     public static final int SERVERPORT = 6000;
 
@@ -167,6 +165,7 @@
     public static final String TRIGGER_AF_KEY = "af";
     public static final String VIB_PATTERN_KEY = "pattern";
     public static final String EVCOMP_KEY = "evComp";
+    public static final String AUTO_FLASH_KEY = "autoFlash";
     public static final String AUDIO_RESTRICTION_MODE_KEY = "mode";
 
     private CameraManager mCameraManager = null;
@@ -737,9 +736,11 @@
                     doCheckStreamCombination(cmdObj);
                 } else if ("isCameraPrivacyModeSupported".equals(cmdObj.getString("cmdName"))) {
                     doCheckCameraPrivacyModeSupport();
-                } else if ("isPerformanceClassPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
+                } else if ("isPrimaryCamera".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
-                    doCheckPerformanceClassPrimaryCamera(cameraId);
+                    doCheckPrimaryCamera(cameraId);
+                } else if ("isPerformanceClass".equals(cmdObj.getString("cmdName"))) {
+                    doCheckPerformanceClass();
                 } else if ("measureCameraLaunchMs".equals(cmdObj.getString("cmdName"))) {
                     String cameraId = cmdObj.getString("cameraId");
                     doMeasureCameraLaunchMs(cameraId);
@@ -1082,10 +1083,7 @@
                 hasPrivacySupport ? "true" : "false");
     }
 
-    private void doCheckPerformanceClassPrimaryCamera(String cameraId) throws ItsException {
-        boolean  isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_S
-                || Build.VERSION.MEDIA_PERFORMANCE_CLASS == PERFORMANCE_CLASS_R);
-
+    private void doCheckPrimaryCamera(String cameraId) throws ItsException {
         if (mItsCameraIdList == null) {
             mItsCameraIdList = ItsUtils.getItsCompatibleCameraIds(mCameraManager);
         }
@@ -1116,8 +1114,15 @@
             throw new ItsException("Failed to get camera characteristics", e);
         }
 
-        mSocketRunnableObj.sendResponse("performanceClassPrimaryCamera",
-                (isPerfClass && isPrimaryCamera) ? "true" : "false");
+        mSocketRunnableObj.sendResponse("primaryCamera",
+                isPrimaryCamera ? "true" : "false");
+    }
+
+    private void doCheckPerformanceClass() throws ItsException {
+        boolean  isPerfClass = (Build.VERSION.MEDIA_PERFORMANCE_CLASS >= PERFORMANCE_CLASS_R);
+
+        mSocketRunnableObj.sendResponse("performanceClass",
+                isPerfClass ? "true" : "false");
     }
 
     private double invokeCameraPerformanceTest(Class testClass, String testName,
@@ -1289,6 +1294,12 @@
                 Logt.i(TAG, String.format("Running 3A with AE exposure compensation value: %d", evComp));
             }
 
+            // Auto flash can be specified as part of AE convergence.
+            boolean autoFlash = params.optBoolean(AUTO_FLASH_KEY, false);
+            if (autoFlash == true) {
+                Logt.i(TAG, String.format("Running with auto flash mode."));
+            }
+
             // By default, AE and AF both get triggered, but the user can optionally override this.
             // Also, AF won't get triggered if the lens is fixed-focus.
             boolean doAE = true;
@@ -1362,8 +1373,6 @@
                         req.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
                         req.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
                                 CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
-                        req.set(CaptureRequest.CONTROL_AE_MODE,
-                                CaptureRequest.CONTROL_AE_MODE_ON);
                         req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
                         req.set(CaptureRequest.CONTROL_AE_LOCK, false);
                         req.set(CaptureRequest.CONTROL_AE_REGIONS, regionAE);
@@ -1382,6 +1391,14 @@
                             req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evComp);
                         }
 
+                        if (autoFlash == false) {
+                            req.set(CaptureRequest.CONTROL_AE_MODE,
+                                    CaptureRequest.CONTROL_AE_MODE_ON);
+                        } else {
+                            req.set(CaptureRequest.CONTROL_AE_MODE,
+                                    CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
+                        }
+
                         if (mConvergedAE && mNeedsLockedAE) {
                             req.set(CaptureRequest.CONTROL_AE_LOCK, true);
                         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
index 08cd8b2..0f75511 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -41,6 +41,9 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.FileNotFoundException;
@@ -77,6 +80,17 @@
             Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
     private static final int MAX_SUMMARY_LEN = 200;
 
+    private static final int MPC12_CAMERA_LAUNCH_THRESHOLD = 600; // ms
+    private static final int MPC12_JPEG_CAPTURE_THRESHOLD = 1000; // ms
+
+    private static final String MPC_TESTS_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
+    private static final String MPC_TESTS_REPORT_LOG_SECTION = "CameraIts";
+
+    private static final Pattern MPC12_CAMERA_LAUNCH_PATTERN =
+            Pattern.compile("camera_launch_time_ms:(\\d+(\\.\\d+)?)");
+    private static final Pattern MPC12_JPEG_CAPTURE_PATTERN =
+            Pattern.compile("1080p_jpeg_capture_time_ms:(\\d+(\\.\\d+)?)");
+
     private final ResultReceiver mResultsReceiver = new ResultReceiver();
     private boolean mReceiverRegistered = false;
 
@@ -97,7 +111,6 @@
             add("scene4");
             add("scene5");
             add("scene6");
-            add("scene_change");
             add("sensor_fusion");
         }};
     // This must match scenes of SUB_CAMERA_TESTS in tools/run_all_tests.py
@@ -116,6 +129,10 @@
     private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
     // map camera id to ITS summary report path
     private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
+    // All primary cameras for which MPC level test has run
+    private Set<ResultKey> mExecutedMpcTests = null;
+    // Map primary camera id to MPC level
+    private final HashMap<String, Integer> mMpcLevelMap = new HashMap<>();
 
     final class ResultKey {
         public final String cameraId;
@@ -213,7 +230,6 @@
 
                     // Update test execution results
                     for (String scene : scenes) {
-                        HashMap<String, String> executedTests = new HashMap<>();
                         JSONObject sceneResult = jsonResults.getJSONObject(scene);
                         Log.v(TAG, sceneResult.toString());
                         String result = sceneResult.getString("result");
@@ -241,6 +257,22 @@
                                 mSummaryMap.put(key, summary);
                             }
                         } // do nothing for NOT_EXECUTED scenes
+
+                        if (sceneResult.isNull("mpc_metrics")) {
+                            continue;
+                        }
+                        // Update MPC level
+                        JSONArray metrics = sceneResult.getJSONArray("mpc_metrics");
+                        for (int i = 0; i < metrics.length(); i++) {
+                            String mpcResult = metrics.getString(i);
+                            if (!matchMpcResult(cameraId, mpcResult, MPC12_CAMERA_LAUNCH_PATTERN,
+                                    "2.2.7.2/7.5/H-1-6", MPC12_CAMERA_LAUNCH_THRESHOLD) &&
+                                    !matchMpcResult(cameraId, mpcResult, MPC12_JPEG_CAPTURE_PATTERN,
+                                    "2.2.7.2/7.5/H-1-5", MPC12_JPEG_CAPTURE_THRESHOLD)) {
+                                Log.e(TAG, "Error parsing MPC result string:" + mpcResult);
+                                return;
+                            }
+                        }
                     }
                 } catch (org.json.JSONException e) {
                     Log.e(TAG, "Error reading json result string:" + results , e);
@@ -249,6 +281,7 @@
 
                 // Set summary if all scenes reported
                 if (mSummaryMap.keySet().containsAll(mAllScenes)) {
+                    // Save test summary
                     StringBuilder summary = new StringBuilder();
                     for (String path : mSummaryMap.values()) {
                         appendFileContentToSummary(summary, path);
@@ -260,6 +293,17 @@
                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
                 }
 
+                //  Save MPC info once both front primary and rear primary data are collected.
+                if (mExecutedMpcTests.size() == 4) {
+                    ItsTestActivity.this.getReportLog().addValue(
+                            "Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
+                    for (Map.Entry<String, Integer> entry : mMpcLevelMap.entrySet()) {
+                        ItsTestActivity.this.getReportLog().addValue(entry.getKey(),
+                                entry.getValue(), ResultType.NEUTRAL, ResultUnit.NONE);
+                    }
+                    ItsTestActivity.this.getReportLog().submit();
+                }
+
                 // Display current progress
                 StringBuilder progress = new StringBuilder();
                 for (ResultKey k : mAllScenes) {
@@ -321,6 +365,29 @@
                 }
             }
         }
+
+        private boolean matchMpcResult(String cameraId, String mpcResult, Pattern pattern,
+                String reqNum, float threshold) {
+            Matcher matcher = pattern.matcher(mpcResult);
+            boolean match = matcher.matches();
+
+            if (match) {
+                // Store test result
+                ItsTestActivity.this.getReportLog().addValue("Cam" + cameraId,
+                        mpcResult, ResultType.NEUTRAL, ResultUnit.NONE);
+
+                float latency = Float.parseFloat(matcher.group(1));
+                int mpcLevel = latency < threshold ? 31 : 0;
+                mExecutedMpcTests.add(new ResultKey(cameraId, reqNum));
+
+                if (mMpcLevelMap.containsKey(reqNum)) {
+                    mpcLevel = Math.min(mpcLevel, mMpcLevelMap.get(reqNum));
+                }
+                mMpcLevelMap.put(reqNum, mpcLevel);
+            }
+
+            return match;
+        }
     }
 
     @Override
@@ -388,6 +455,9 @@
                 testTitle(cam, scene),
                 testId(cam, scene)));
             }
+            if (mExecutedMpcTests == null) {
+                mExecutedMpcTests = new TreeSet<>(mComparator);
+            }
             Log.d(TAG,"Total combinations to test on this device:" + mAllScenes.size());
         }
     }
@@ -427,4 +497,14 @@
         setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
         setPassFailButtonClickListeners();
     }
+
+    @Override
+    public String getReportFileName() {
+        return MPC_TESTS_REPORT_LOG_NAME;
+    }
+
+    @Override
+    public String getReportSectionName() {
+        return MPC_TESTS_REPORT_LOG_SECTION;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
index a7f742f..06e5ea3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeChecker.java
@@ -29,11 +29,11 @@
 import android.os.Handler;
 import android.os.Message;
 import android.os.PersistableBundle;
+import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 
 import java.lang.ref.WeakReference;
-import java.util.List;
 
 public final class GarageModeChecker extends JobService {
     private static final String TAG = GarageModeChecker.class.getSimpleName();
@@ -45,6 +45,7 @@
     static final String PREFS_TERMINATION = "termination-time";
     static final String PREFS_JOB_UPDATE = "job-update-time";
     static final String PREFS_HAD_CONNECTIVITY = "had-connectivity";
+    static final String PREFS_START_BOOT_COUNT = "start-boot-count";
 
     static final int SECONDS_PER_ITERATION = 10;
     static final int MS_PER_ITERATION = SECONDS_PER_ITERATION * 1000;
@@ -172,6 +173,17 @@
         return true;
     }
 
+    static int getBootCount(Context context) {
+        int bootCount = 0;
+        try {
+            bootCount = Settings.Global.getInt(context.getContentResolver(),
+                    Settings.Global.BOOT_COUNT);
+        } catch (Settings.SettingNotFoundException e) {
+            Log.e(TAG, "Could not get Settings.Global.BOOT_COUNT: ", e);
+        }
+        return bootCount;
+    }
+
     private final class GarageModeCheckerTask extends AsyncTask<Void, Void, Boolean> {
         private final WeakReference<Handler> mHandler;
         private final JobParameters mJobParameter;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
index ac13ba2..a4a2930 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GarageModeTestActivity.java
@@ -90,6 +90,8 @@
             editor.putLong(GarageModeChecker.PREFS_GARAGE_MODE_END, 0);
             editor.putLong(GarageModeChecker.PREFS_TERMINATION, 0);
             editor.putBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+            editor.putLong(GarageModeChecker.PREFS_START_BOOT_COUNT,
+                    GarageModeChecker.getBootCount(context));
             editor.commit();
 
             GarageModeChecker.scheduleAnIdleJob(context, NUM_SECONDS_DURATION);
@@ -131,6 +133,8 @@
         long termination = prefs.getLong(GarageModeChecker.PREFS_TERMINATION, 0);
         long jobUpdate = prefs.getLong(GarageModeChecker.PREFS_JOB_UPDATE, 0);
         boolean hadConnectivity = prefs.getBoolean(GarageModeChecker.PREFS_HAD_CONNECTIVITY, false);
+        long startBootCount = prefs.getLong(GarageModeChecker.PREFS_START_BOOT_COUNT, 0);
+        long currentBootCount = GarageModeChecker.getBootCount(context);
 
         boolean testPassed = false;
         if (initiateTime == 0) {
@@ -185,6 +189,12 @@
                     (hadConnectivity ? "" : "not "),
                     dateTime.format(initiateTime), dateTime.format(garageModeStart),
                     dateTime.format(termination));
+        }  else if ((garageModeStart > 0 && garageModeEnd == 0)
+                                        && (currentBootCount - startBootCount > 0)) {
+            resultsString = "Test failed.\n\n"
+                    + "Garage Mode started, but system restarted before it completed.\n\n"
+                    + dateTime.format(initiateTime) + " -- Test was enabled\n"
+                    + dateTime.format(garageModeStart) + " -- Garage mode started";
         } else if (now < jobUpdate + GarageModeChecker.MS_PER_ITERATION * 2) {
             resultsString = "Garage Mode started and test is running.\n\n"
                     + dateTime.format(initiateTime) + " -- Test was enabled\n"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
index c926d1a..558c411 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/car/GearSelectionTestActivity.java
@@ -43,7 +43,6 @@
     private static final long TEST_TIMEOUT_MINUTES = 10;
 
     private List<Integer> mSupportedGears;
-    private Integer mGearsAchievedCount = 0;
     private TextView mExpectedGearSelectionTextView;
     private TextView mCurrentGearSelectionTextView;
     private CarPropertyManager mCarPropertyManager;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
index aaea279..2cafd94 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/deskclock/DeskClockTestsActivity.java
@@ -6,15 +6,16 @@
 import android.database.DataSetObserver;
 import android.os.Bundle;
 import android.provider.AlarmClock;
+import android.util.Log;
 
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.IntentDrivenTestActivity;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.TestListAdapter.TestListItem;
 import com.android.cts.verifier.IntentDrivenTestActivity.ButtonInfo;
 import com.android.cts.verifier.IntentDrivenTestActivity.IntentFactory;
 import com.android.cts.verifier.IntentDrivenTestActivity.TestInfo;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestListAdapter.TestListItem;
 
 import java.util.ArrayList;
 import java.util.Calendar;
@@ -24,6 +25,8 @@
  */
 public class DeskClockTestsActivity extends PassFailButtons.TestListActivity {
 
+    private static final String TAG = DeskClockTestsActivity.class.getSimpleName();
+
     private static final String SHOW_ALARMS_TEST = "SHOW_ALARMS";
     public static final String SET_ALARM_WITH_UI_TEST = "SET_ALARM_WITH_UI";
     public static final String START_ALARM_TEST = "START_ALARM";
@@ -153,15 +156,13 @@
 
     private void addTests(ArrayTestListAdapter adapter, TestInfo[] tests) {
         for (TestInfo info : tests) {
-
-            final int title = info.getTitle();
-            adapter.add(TestListItem.newTest(this, title, info.getTestId(),
-            new Intent(this, IntentDrivenTestActivity.class)
-                    .putExtra(IntentDrivenTestActivity.EXTRA_ID, info.getTestId())
-                    .putExtra(IntentDrivenTestActivity.EXTRA_TITLE, title)
-                    .putExtra(IntentDrivenTestActivity.EXTRA_INFO, info.getInfoText())
-                    .putExtra(IntentDrivenTestActivity.EXTRA_BUTTONS, info.getButtons()),
-                    null));
+            int title = info.getTitle();
+            String testId = info.getTestId();
+            Intent intent = IntentDrivenTestActivity.newIntent(this, testId, title,
+                    info.getInfoText(), info.getButtons());
+            Log.d(TAG, "Adding test with " + IntentDrivenTestActivity.toString(this, intent));
+            adapter.add(TestListItem.newTest(this, title, testId, intent,
+                    /* applicableFeatures= */ null));
         }
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index 218897f..489336f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -382,11 +382,29 @@
                     uninstallHelperPackage();
                 } break;
                 case COMMAND_SET_PERMISSION_GRANT_STATE: {
-                    Log.d(TAG, "Granting permission using " + mDpm);
-                    mDpm.setPermissionGrantState(mAdmin, getPackageName(),
-                            intent.getStringExtra(EXTRA_PERMISSION),
-                            intent.getIntExtra(EXTRA_GRANT_STATE,
-                                    DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT));
+                    String pkgName = getPackageName();
+                    String permission = intent.getStringExtra(EXTRA_PERMISSION);
+                    int grantState = intent.getIntExtra(EXTRA_GRANT_STATE,
+                            DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT);
+                    String action;
+                    switch (grantState) {
+                        case DevicePolicyManager.PERMISSION_GRANT_STATE_GRANTED:
+                            action = "Granting " + permission;
+                            break;
+                        case DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED:
+                            action = "Denying " + permission;
+                            break;
+                        case DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT:
+                            action = "Setting " + permission + " to default state";
+                            break;
+                        default:
+                            action = "Setting grantState of " + permission + " to " + grantState;
+                    }
+                    Log.d(TAG, action + " to " + pkgName + " using " + mDpm);
+                    int stateBefore = mDpm.getPermissionGrantState(mAdmin, pkgName, permission);
+                    mDpm.setPermissionGrantState(mAdmin, pkgName, permission, grantState);
+                    int stateAfter = mDpm.getPermissionGrantState(mAdmin, pkgName, permission);
+                    Log.d(TAG, "Grant state: before=" + stateBefore + ", after=" + stateAfter);
                 } break;
                 case COMMAND_ADD_PERSISTENT_PREFERRED_ACTIVITIES: {
                     final ComponentName componentName =
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
index 70aaab5..09c1c9c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/IntentFiltersTestHelper.java
@@ -16,34 +16,27 @@
 
 package com.android.cts.verifier.managedprovisioning;
 
-import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
 import android.app.DownloadManager;
-import android.content.ComponentName;
+import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.media.audiofx.AudioEffect;
 import android.net.Uri;
 import android.nfc.cardemulation.CardEmulation;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Environment;
-import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.AlarmClock;
 import android.provider.CalendarContract.Events;
 import android.provider.MediaStore;
 import android.provider.Settings;
-import android.speech.RecognizerIntent;
 import android.util.Log;
-import android.widget.Toast;
 
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -161,7 +154,7 @@
         PackageManager pm = mContext.getPackageManager();
 
         if (pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                && pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+                && pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
             forwardedIntentsFromManaged.addAll(Arrays.asList(
                     new Intent("android.intent.action.CALL_EMERGENCY").setData(
                             Uri.parse("tel:123")),
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
index 0ffd259..e91f92b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -39,6 +39,7 @@
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
 import android.app.Person;
 import android.content.Context;
 import android.content.Intent;
@@ -122,6 +123,12 @@
         tests.add(new IsEnabledTest());
         tests.add(new ServiceStartedTest());
         tests.add(new NotificationReceivedTest());
+        if (!isAutomotive) {
+            tests.add(new SendUserToChangeFilter());
+            tests.add(new AskIfFilterChanged());
+            tests.add(new NotificationTypeFilterTest());
+            tests.add(new ResetChangeFilter());
+        }
         tests.add(new LongMessageTest());
         tests.add(new DataIntactTest());
         tests.add(new AudiblyAlertedTest());
@@ -146,6 +153,7 @@
         tests.add(new RequestBindTest());
         tests.add(new MessageBundleTest());
         tests.add(new ConversationOrderingTest());
+        tests.add(new HunDisplayTest());
         tests.add(new EnableHintsTest());
         tests.add(new IsDisabledTest());
         tests.add(new ServiceStoppedTest());
@@ -1694,6 +1702,114 @@
         }
     }
 
+    /**
+     * Tests that heads-up notifications appear with the view, resources, and actions provided
+     * in Notification.Builder.
+     */
+    private class HunDisplayTest extends InteractiveTestCase {
+
+        @Override
+        protected void setUp() {
+            createChannels();
+            sendNotifications();
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            deleteChannels();
+            delay();
+        }
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createPassFailItem(parent, R.string.hun_display);
+        }
+
+        private void sendNotifications() {
+            mTag1 = UUID.randomUUID().toString();
+            mId1 = NOTIFICATION_ID + 1;
+
+            Notification n1 = new Notification.Builder(mContext, NOISY_NOTIFICATION_CHANNEL_ID)
+                    .setContentTitle("HunDisplayTest")
+                    .setContentText(mTag1)
+                    .setSmallIcon(R.drawable.ic_stat_alice)
+                    .setLargeIcon(Icon.createWithResource(mContext, R.drawable.test_pass_gradient))
+                    .addAction(generateAction(1))
+                    .addAction(generateAction(2))
+                    .build();
+            mNm.notify(mTag1, mId1, n1);
+        }
+
+        private Notification.Action generateAction(int num) {
+            PendingIntent pi = PendingIntent.getActivity(mContext, num,
+                    new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS),
+                    PendingIntent.FLAG_IMMUTABLE);
+            return new Notification.Action.Builder(
+                    Icon.createWithResource(mContext, R.drawable.ic_android),
+                    mContext.getString(R.string.action, num), pi)
+                    .build();
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    /**
+     * Sends the user to settings filter out silent notifications for this notification listener.
+     * Sends silent and not silent notifs and makes sure only the non silent is received
+     */
+    private class NotificationTypeFilterTest extends InteractiveTestCase {
+        int mRetries = 3;
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_filter_test);
+
+        }
+
+        @Override
+        protected void setUp() {
+            createChannels();
+            sendNotifications();
+            sendNoisyNotification();
+            status = READY;
+        }
+
+        @Override
+        protected void tearDown() {
+            mNm.cancelAll();
+            MockListener.getInstance().resetData();
+            deleteChannels();
+        }
+
+        @Override
+        protected void test() {
+            if (MockListener.getInstance().getPosted(mTag4) == null) {
+                Log.d(TAG, "Could not find " + mTag4);
+                if (--mRetries > 0) {
+                    sleep(100);
+                    status = RETEST;
+                } else {
+                    status = FAIL;
+                }
+            } else if (MockListener.getInstance().getPosted(mTag2) != null) {
+                logFail("Found" + mTag2);
+                status = FAIL;
+            } else {
+                status = PASS;
+            }
+        }
+    }
+
     protected class SendUserToChangeFilter extends InteractiveTestCase {
         @Override
         protected View inflate(ViewGroup parent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
index 542fb36..f3e2680 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/RequesterTestActivity.java
@@ -15,10 +15,6 @@
  */
 package com.android.cts.verifier.p2p;
 
-import java.util.Collection;
-import java.util.Timer;
-import java.util.TimerTask;
-
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -40,6 +36,10 @@
 import com.android.cts.verifier.p2p.testcase.TestCase;
 import com.android.cts.verifier.p2p.testcase.TestCase.TestCaseListener;
 
+import java.util.Collection;
+import java.util.Timer;
+import java.util.TimerTask;
+
 /**
  * A base class for requester test activity.
  *
@@ -135,8 +135,8 @@
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
+    protected void onStart() {
+        super.onStart();
         /*
          * If the target device is NOT set, search targets and show
          * the target device list on the dialog.
@@ -152,8 +152,8 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    protected void onStop() {
+        super.onStop();
         if (mTimer != null) {
             mTimer.cancel();
             mTimer = null;
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
index 39f0bf8..405d4be 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/p2p/ResponderTestActivity.java
@@ -101,8 +101,8 @@
     }
 
     @Override
-    protected void onResume() {
-        super.onResume();
+    protected void onStart() {
+        super.onStart();
         mTestCase.start(this);
         registerReceiver(mReceiver, mIntentFilter);
         mP2pMgr.requestDeviceInfo(mChannel, wifiP2pDevice -> {
@@ -119,8 +119,8 @@
     }
 
     @Override
-    protected void onPause() {
-        super.onPause();
+    protected void onStop() {
+        super.onStop();
         mTestCase.stop();
         unregisterReceiver(mReceiver);
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
index 5de1d8d..4ce47ef 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/InteractiveVerifierActivity.java
@@ -40,9 +40,6 @@
     private static final String TAG = "InteractiveVerifier";
     private static final String STATE = "state";
     private static final String STATUS = "status";
-    protected static final String TILE_PATH = "com.android.cts.verifier/" +
-            "com.android.cts.verifier.qstiles.MockTileService";
-    protected static final ComponentName TILE_NAME = ComponentName.unflattenFromString(TILE_PATH);
     protected static final int SETUP = 0;
     protected static final int READY = 1;
     protected static final int RETEST = 2;
@@ -66,10 +63,13 @@
     protected boolean setTileState(boolean enabled) {
         int state = enabled ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-        mPackageManager.setComponentEnabledSetting(TILE_NAME, state, PackageManager.DONT_KILL_APP);
-        return mPackageManager.getComponentEnabledSetting(TILE_NAME) == state;
+        mPackageManager.setComponentEnabledSetting(
+                getTileComponentName(), state, PackageManager.DONT_KILL_APP);
+        return mPackageManager.getComponentEnabledSetting(getTileComponentName()) == state;
     }
 
+    protected abstract ComponentName getTileComponentName();
+
     protected abstract class InteractiveTestCase {
         protected boolean mUserVerified;
         protected int status;
@@ -82,6 +82,8 @@
             if (view == null) {
                 view = inflate(parent);
             }
+            View requestAction = view.requireViewById(R.id.tiles_action_request);
+            requestAction.setVisibility(showRequestAction() ? View.VISIBLE : View.GONE);
             return view;
         }
 
@@ -131,6 +133,14 @@
                     ((message == null) ? "" : ": " + message), e);
         }
 
+        protected boolean showRequestAction() {
+            return false;
+        }
+
+        protected void requestAction() {
+
+        }
+
     }
 
     protected abstract int getTitleResource();
@@ -205,8 +215,6 @@
         }
         View item = test.view;
         ImageView status = (ImageView) item.findViewById(R.id.tiles_status);
-        View buttonPass = item.findViewById(R.id.tiles_action_pass);
-        View buttonFail = item.findViewById(R.id.tiles_action_fail);
         switch (test.status) {
             case WAIT_FOR_USER:
                 status.setImageResource(R.drawable.fs_warning);
@@ -216,32 +224,35 @@
             case READY:
             case RETEST:
                 status.setImageResource(R.drawable.fs_clock);
-                buttonPass.setEnabled(true);
-                buttonPass.setClickable(true);
-                buttonFail.setEnabled(true);
-                buttonFail.setClickable(true);
+                setButtonsState(item, true);
                 break;
 
             case FAIL:
                 status.setImageResource(R.drawable.fs_error);
-                buttonFail.setClickable(false);
-                buttonFail.setEnabled(false);
-                buttonPass.setClickable(false);
-                buttonPass.setEnabled(false);
+                setButtonsState(item, false);
                 break;
 
             case PASS:
                 status.setImageResource(R.drawable.fs_good);
-                buttonFail.setClickable(false);
-                buttonFail.setEnabled(false);
-                buttonPass.setClickable(false);
-                buttonPass.setEnabled(false);
+                setButtonsState(item, false);
                 break;
 
         }
         status.invalidate();
     }
 
+    private void setButtonsState(View parent, boolean enabledAndClickable) {
+        View buttonPass = parent.findViewById(R.id.tiles_action_pass);
+        View buttonFail = parent.findViewById(R.id.tiles_action_fail);
+        View buttonRequest = parent.findViewById(R.id.tiles_action_request);
+
+        buttonPass.setEnabled(enabledAndClickable);
+        buttonPass.setClickable(enabledAndClickable);
+        buttonFail.setEnabled(enabledAndClickable);
+        buttonFail.setClickable(enabledAndClickable);
+        buttonRequest.setEnabled(enabledAndClickable);
+        buttonRequest.setClickable(enabledAndClickable);
+    }
 
     protected View createUserPassFail(ViewGroup parent, int messageId,
             Object... messageFormatArgs) {
@@ -379,6 +390,12 @@
                     mCurrentTest.mUserVerified = true;
                     next();
                     break;
+                case R.id.tiles_action_request:
+                    mCurrentTest.status = WAIT_FOR_USER;
+                    v.setEnabled(false);
+                    mHandler.post(mCurrentTest::requestAction);
+                    next();
+                    break;
                 default:
                     break;
             }
@@ -393,4 +410,10 @@
         Log.e(TAG, message, stackTrace);
     }
 
+    protected void setPassFailButtonsEnabledState(boolean enabled) {
+        View currentView = mCurrentTest.view;
+        currentView.requireViewById(R.id.tiles_action_pass).setEnabled(enabled);
+        currentView.requireViewById(R.id.tiles_action_fail).setEnabled(enabled);
+    }
+
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
index cbc5f31..db047da 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
@@ -1,3 +1,4 @@
 # Bug component: 78930
 juliacr@google.com
-kozynski@google.com
\ No newline at end of file
+kozynski@google.com
+evanlaird@google.com
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java
new file mode 100644
index 0000000..ddaab1b
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceRequestVerifierActivity.java
@@ -0,0 +1,415 @@
+/*
+ * 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.verifier.qstiles;
+
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInstaller;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+public class TileServiceRequestVerifierActivity extends InteractiveVerifierActivity {
+
+    private static final String TAG = "TileServiceRequestVerifierActivity";
+
+    private static final String ACTION_REMOVE_PACKAGE =
+            "com.android.cts.verifier.qstiles.ACTION_REMOVE_PACKAGE";
+
+    private CharSequence mTileLabel;
+    private static int sNextResultCode = 1000;
+    private static final String HELPER_PACKAGE_NAME = "com.android.cts.tileserviceapp";
+    private static final String HELPER_ACTIVITY_NAME = ".TileRequestActivity";
+    private static final String HELPER_TILE_NAME = ".RequestTileService";
+    private static final ComponentName HELPER_ACTIVITY_COMPONENT =
+            ComponentName.createRelative(HELPER_PACKAGE_NAME, HELPER_ACTIVITY_NAME);
+    private static final Intent INTENT = new Intent().setComponent(HELPER_ACTIVITY_COMPONENT);
+
+    // Keep track of activity started codes to handle results.
+    private final Map<Integer, Consumer<Integer>> mResultRegistry = new HashMap<>();
+
+    @Override
+    protected boolean setTileState(boolean enabled) {
+        // This tile is always enabled as long as the package is installed.
+        return true;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedState) {
+        mTileLabel = getString(R.string.tile_request_service_name);
+        super.onCreate(savedState);
+    }
+
+    private void registerForResult(Consumer<Integer> consumer) {
+        int code = sNextResultCode++;
+        mResultRegistry.put(code, consumer);
+        startActivityForResult(INTENT, code);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        mResultRegistry.get(requestCode).accept(resultCode);
+    }
+
+    private boolean isHelperAppInstalled() {
+        return getPackageManager().resolveActivity(INTENT, 0) != null;
+    }
+
+    @Override
+    protected ComponentName getTileComponentName() {
+        return ComponentName.createRelative(HELPER_PACKAGE_NAME, HELPER_TILE_NAME);
+    }
+
+    @Override
+    protected int getTitleResource() {
+        return R.string.tiles_request_test;
+    }
+
+    @Override
+    protected int getInstructionsResource() {
+        return R.string.tiles_request_info;
+    }
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        ArrayList<InteractiveTestCase> list = new ArrayList<>();
+        list.add(new UninstallPackage());
+        list.add(new InstallPackage());
+        list.add(new InstallPackageVerify());
+        list.add(new TileNotPresent());
+        list.add(new RequestAddTileDismiss());
+        list.add(new RequestAddTileAnswerNo());
+        list.add(new RequestAddTileCorrectInfo());
+        list.add(new RequestAddTileAnswerYes());
+        list.add(new TilePresentAfterRequest());
+        list.add(new RequestAddTileAlreadyAdded());
+        list.add(new UninstallPackage());
+        return list;
+    }
+
+    // Tests
+    private class UninstallPackage extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_uninstall);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected boolean showRequestAction() {
+            return true;
+        }
+
+        private BroadcastReceiver registerReceiver() {
+            BroadcastReceiver br = new BroadcastReceiver() {
+                @Override
+                public void onReceive(Context context, Intent intent) {
+                    int result = intent.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                            PackageInstaller.STATUS_SUCCESS);
+                    if (result == PackageInstaller.STATUS_PENDING_USER_ACTION) {
+                        startActivity(intent.getParcelableExtra(Intent.EXTRA_INTENT));
+                        return;
+                    }
+                    context.unregisterReceiver(this);
+                    if (!isHelperAppInstalled()) {
+                        status = PASS;
+                    } else {
+                        setFailed("Helper App still installed");
+                    }
+                    next();
+                }
+            };
+            mContext.registerReceiver(br, new IntentFilter(ACTION_REMOVE_PACKAGE));
+            return br;
+        }
+
+        @Override
+        protected void requestAction() {
+            PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
+            Log.i(TAG,
+                    "Uninstalling package " + HELPER_PACKAGE_NAME + " using " + packageInstaller);
+            BroadcastReceiver br = registerReceiver();
+            try {
+                PendingIntent pi = PendingIntent.getBroadcast(mContext, /* requestCode */ 0,
+                        new Intent(ACTION_REMOVE_PACKAGE), PendingIntent.FLAG_MUTABLE);
+                packageInstaller.uninstall(HELPER_ACTIVITY_COMPONENT.getPackageName(),
+                        pi.getIntentSender());
+                status = WAIT_FOR_USER;
+            } catch (IllegalArgumentException e) {
+                mContext.unregisterReceiver(br);
+                status = PASS;
+            }
+        }
+
+        @Override
+        protected void test() {
+            if (status == READY) {
+                if (!isHelperAppInstalled()) {
+                    status = PASS;
+                } else {
+                    status = WAIT_FOR_USER;
+                }
+                next();
+            }
+        }
+    }
+
+    private class InstallPackage extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserPassFail(parent, R.string.tiles_request_install);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class InstallPackageVerify extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_install_verify);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        protected void test() {
+            if (isHelperAppInstalled()) {
+                status = PASS;
+            } else {
+                setFailed("Helper app not properly installed");
+            }
+            next();
+        }
+    }
+
+    private class TileNotPresent extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserPassFail(parent, R.string.tiles_request_tile_not_present, mTileLabel);
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RequestAddTileDismiss extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_dismissed);
+        }
+
+        @Override
+        protected boolean showRequestAction() {
+            return true;
+        }
+
+        @Override
+        protected void requestAction() {
+            registerForResult(
+                    integer -> {
+                        if (integer.equals(
+                                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+                            status = PASS;
+                        } else {
+                            setFailed("Request called back with result: " + integer);
+                        }
+                        next();
+                    }
+            );
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RequestAddTileAnswerNo extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_answer_no);
+        }
+
+        @Override
+        protected boolean showRequestAction() {
+            return true;
+        }
+
+        @Override
+        protected void requestAction() {
+            registerForResult(
+                    integer -> {
+                        if (integer.equals(
+                                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+                            status = PASS;
+                        } else {
+                            setFailed("Request called back with result: " + integer);
+                        }
+                        next();
+                    }
+            );
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RequestAddTileCorrectInfo extends InteractiveTestCase {
+
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserPassFail(parent, R.string.tiles_request_correct_info,
+                    mContext.getString(R.string.tile_request_helper_app_name),
+                    mTileLabel);
+        }
+
+        @Override
+        protected boolean showRequestAction() {
+            return true;
+        }
+
+        @Override
+        protected void requestAction() {
+            registerForResult(
+                    integer -> {
+                        if (integer.equals(
+                                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED)) {
+                            status = WAIT_FOR_USER;
+                            setPassFailButtonsEnabledState(true);
+                        } else {
+                            setFailed("Request called back with result: " + integer);
+                        }
+                        next();
+                    }
+            );
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            setPassFailButtonsEnabledState(false);
+            next();
+        }
+    }
+
+    private class RequestAddTileAnswerYes extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_answer_yes);
+        }
+
+        @Override
+        protected boolean showRequestAction() {
+            return true;
+        }
+
+        @Override
+        protected void requestAction() {
+            registerForResult(
+                    integer -> {
+                        if (integer.equals(StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ADDED)) {
+                            status = PASS;
+                        } else {
+                            setFailed("Request called back with result: " + integer);
+                        }
+                        next();
+                    }
+            );
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class TilePresentAfterRequest extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createUserPassFail(parent, R.string.tiles_request_tile_present, mTileLabel);
+        }
+
+        @Override
+        protected void test() {
+            status = WAIT_FOR_USER;
+            next();
+        }
+    }
+
+    private class RequestAddTileAlreadyAdded extends InteractiveTestCase {
+        @Override
+        protected View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.tiles_request_check_tile_already_added);
+        }
+
+        @Override
+        protected void test() {
+            registerForResult(
+                    integer -> {
+                        if (integer.equals(
+                                StatusBarManager.TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED)) {
+                            status = PASS;
+                        } else {
+                            setFailed("Request called back with result: " + integer);
+                        }
+                        next();
+                    }
+            );
+            status = READY_AFTER_LONG_DELAY;
+            next();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
index a65156c..3071b65 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/qstiles/TileServiceVerifierActivity.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.qstiles;
 
+import android.content.ComponentName;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -37,6 +38,13 @@
     }
 
     @Override
+    protected ComponentName getTileComponentName() {
+        String tilePath = "com.android.cts.verifier/"
+                + "com.android.cts.verifier.qstiles.MockTileService";
+        return ComponentName.unflattenFromString(tilePath);
+    }
+
+    @Override
     protected List<InteractiveTestCase> createTestItems() {
         ArrayList<InteractiveTestCase> list = new ArrayList<>();
         list.add(new SettingUpTile());
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
index 9bad7af..cb18251 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/DeviceSuspendTestActivity.java
@@ -1,18 +1,6 @@
 package com.android.cts.verifier.sensors;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
-import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
-
 import android.app.AlarmManager;
-import android.app.IntentService;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -23,33 +11,26 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
-import android.hardware.TriggerEvent;
-import android.hardware.TriggerEventListener;
-import android.hardware.cts.helpers.MovementDetectorHelper;
-import android.hardware.cts.helpers.SensorStats;
-import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorNotSupportedException;
 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
 import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEvent;
-import android.hardware.cts.helpers.TestSensorEventListener;
-import android.hardware.cts.helpers.TestSensorManager;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.SensorNotSupportedException;
 import android.hardware.cts.helpers.sensorverification.BatchArrivalVerification;
 import android.hardware.cts.helpers.sensorverification.TimestampClockSourceVerification;
 import android.os.IBinder;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeLock;
 import android.os.SystemClock;
-import androidx.localbroadcastmanager.content.LocalBroadcastManager;
 import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
 
-import junit.framework.Assert;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.sensors.base.SensorCtsVerifierTestActivity;
+import com.android.cts.verifier.sensors.helpers.SensorTestScreenManipulator;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 public class DeviceSuspendTestActivity
             extends SensorCtsVerifierTestActivity {
@@ -474,17 +455,10 @@
 
 
         public String runAPWakeUpByAlarmNonWakeSensor(Sensor sensor, int maxReportLatencyUs)
-            throws  Throwable {
+                throws Throwable {
             verifyBatchingSupport(sensor);
 
-            int samplingPeriodUs = sensor.getMaxDelay();
-            if (samplingPeriodUs == 0 || samplingPeriodUs > 200000) {
-                // If maxDelay is not defined, set the value for 5 Hz.
-                samplingPeriodUs = 200000;
-            }
-
-            long fifoBasedReportLatencyUs = maxBatchingPeriod(sensor, samplingPeriodUs);
-            verifyBatchingPeriod(fifoBasedReportLatencyUs);
+            int samplingPeriodUs = sensor.getMinDelay();
 
             TestSensorEnvironment environment = new TestSensorEnvironment(
                     this,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 5b19e7b..9451c60 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -68,7 +68,7 @@
     private static final int CAPABILITIES_CHANGED_FOR_METERED_TIMEOUT_MS = 80_000;
 
     private final Object mLock = new Object();
-    private final ScheduledExecutorService mExecutorService;
+    private static ScheduledExecutorService sExecutorService;
     private final WifiNetworkSuggestion.Builder mNetworkSuggestionBuilder =
             new WifiNetworkSuggestion.Builder();
 
@@ -101,7 +101,6 @@
             boolean setRequiresAppInteraction, boolean simulateConnectionFailure,
             boolean setMeteredPostConnection) {
         super(context);
-        mExecutorService = Executors.newSingleThreadScheduledExecutor();
         mSetBssid = setBssid;
         mSetRequiresAppInteraction = setRequiresAppInteraction;
         mSimulateConnectionFailure = simulateConnectionFailure;
@@ -299,7 +298,7 @@
 
         // Step: Trigger scans periodically to trigger network selection quicker.
         if (DBG) Log.v(TAG, "Triggering scan periodically");
-        mExecutorService.scheduleAtFixedRate(() -> {
+        sExecutorService.scheduleAtFixedRate(() -> {
             if (!mWifiManager.startScan()) {
                 Log.w(TAG, "Failed to trigger scan");
             }
@@ -459,12 +458,13 @@
     @Override
     protected void setUp() {
         super.setUp();
+        sExecutorService = Executors.newSingleThreadScheduledExecutor();
         mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
     }
 
     @Override
     protected void tearDown() {
-        mExecutorService.shutdownNow();
+        sExecutorService.shutdownNow();
         if (mBroadcastReceiver != null) {
             mContext.unregisterReceiver(mBroadcastReceiver);
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java
new file mode 100644
index 0000000..2d53ad1
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupPublishTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, in-band and force channel setup on publish
+ */
+public class DataPathForceChannelSetupPublishTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
+                /* acceptAny */ false, /* forceChannel */ true);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setInfoResources(R.string.aware_data_path_force_channel_setup_publish,
+                R.string.aware_data_path_force_channel_setup_publish_info, 0);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java
new file mode 100644
index 0000000..502abc8
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathForceChannelSetupSubscribeTestActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.wifiaware;
+
+import android.content.Context;
+
+import com.android.cts.verifier.wifiaware.testcase.DataPathInBandTestCase;
+
+/**
+ * Test activity for data-path, in-band and force channel setup on Subscribe
+ */
+public class DataPathForceChannelSetupSubscribeTestActivity extends BaseTestActivity {
+    @Override
+    protected BaseTestCase getTestCase(Context context) {
+        return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
+                /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
+                /* acceptAny */ false, /* forceChannel */ true);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
index e81528e..682bfcf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeAcceptAnyTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
index 888c4cb..15f9d35 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenActiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
index b25c3ef..21a2cb3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeAcceptAnyTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
index 9bfd9d1..5f5145e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenPassiveSubscribeTestActivity.java
@@ -27,6 +27,7 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ false,
-                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
index 0888e5e..b07d3a0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishAcceptAnyTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ true);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ true,
+                /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
index 2bd5313..e80bf67 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenSolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ false, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
index ed9e17a..25d7153 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishAcceptAnyTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ true);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ true,
+                /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
index 6d7e8ca..06ac548 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathOpenUnsolicitedPublishTestActivity.java
@@ -29,7 +29,8 @@
     @Override
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ true, /* isPublish */ true,
-                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false);
+                /* isUnsolicited */ true, /* usePmk */ false, /* acceptAny */ false,
+                /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
index 62275ec..b166faf 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
index e5f77e9..86a86b9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseActiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
index 88ea2bb..24df500 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
index 46413c3..f0f7b44 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphrasePassiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
index 2a24d85..188cfbd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
-                /* acceptAny */ true);
+                /* acceptAny */ true, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
index 9c06b5d..114862d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseSolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
index 2616357..5ba364a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
-                /* acceptAny */ true);
+                /* acceptAny */ true, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
index 601f75b..1f5666f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPassphraseUnsolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ false,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
index 136b296..0c8c494 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
index 11c1e64..1fc76fc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkActiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ false, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
index 44cab45..b158720 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeAcceptAnyTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
index 28f6c76..8cabd8f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkPassiveSubscribeTestActivity.java
@@ -28,6 +28,6 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ false, /* isUnsolicited */ true, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
index 69cdfd2..fc23c42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
-                /* acceptAny */ true);
+                /* acceptAny */ true, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
index e0c0f29..d01fdbb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkSolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ false, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
index 4c557e9..e4fc753 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishAcceptAnyTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
-                /* acceptAny */ true);
+                /* acceptAny */ true, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
index a9154ec..68267eb 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/DataPathPmkUnsolicitedPublishTestActivity.java
@@ -30,7 +30,7 @@
     protected BaseTestCase getTestCase(Context context) {
         return new DataPathInBandTestCase(context, /* isSecurityOpen */ false,
                 /* isPublish */ true, /* isUnsolicited */ true, /* usePmk */ true,
-                /* acceptAny */ false);
+                /* acceptAny */ false, /* forceChannel */false);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
index b1b62bf..1e81992 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/TestListActivity.java
@@ -232,6 +232,21 @@
                     R.string.aware_subscribe,
                     DataPathPmkActiveSubscribeAcceptAnyTestActivity.class.getName(),
                     new Intent(this, DataPathPmkActiveSubscribeAcceptAnyTestActivity.class), null));
+
+            if (mWifiAwareManager.isSetChannelOnDataPathSupported()) {
+                adapter.add(TestListAdapter.TestListItem.newCategory(this,
+                        R.string.aware_dp_ib_force_channel_setup));
+                adapter.add(TestListAdapter.TestListItem.newTest(this,
+                        R.string.aware_publish,
+                        DataPathForceChannelSetupPublishTestActivity.class.getName(),
+                        new Intent(this, DataPathForceChannelSetupPublishTestActivity.class),
+                        null));
+                adapter.add(TestListAdapter.TestListItem.newTest(this,
+                        R.string.aware_subscribe,
+                        DataPathForceChannelSetupSubscribeTestActivity.class.getName(),
+                        new Intent(this, DataPathForceChannelSetupSubscribeTestActivity.class),
+                        null));
+            }
         }
 
         adapter.registerDataSetObserver(new DataSetObserver() {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
index 737234a..af2441c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathInBandTestCase.java
@@ -24,6 +24,7 @@
 import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.wifi.aware.PublishDiscoverySession;
+import android.net.wifi.aware.WifiAwareChannelInfo;
 import android.net.wifi.aware.WifiAwareNetworkInfo;
 import android.net.wifi.aware.WifiAwareNetworkSpecifier;
 import android.util.Log;
@@ -84,10 +85,13 @@
     private static final byte[] MSG_CLIENT_TO_SERVER = "GET SOME BYTES".getBytes();
     private static final byte[] MSG_SERVER_TO_CLIENT = "PUT SOME OTHER BYTES".getBytes();
 
+    private static final int CHANNEL_IN_MHZ = 2412;
+
     private boolean mIsSecurityOpen;
     private boolean mUsePmk;
     private boolean mIsPublish;
     private boolean mIsAcceptAny;
+    private boolean mForceChannel;
     private Thread mClientServerThread;
     private ConnectivityManager mCm;
     private CallbackUtils.NetworkCb mNetworkCb;
@@ -95,13 +99,14 @@
     private static int sSDKLevel = android.os.Build.VERSION.SDK_INT;
 
     public DataPathInBandTestCase(Context context, boolean isSecurityOpen, boolean isPublish,
-            boolean isUnsolicited, boolean usePmk, boolean acceptAny) {
+            boolean isUnsolicited, boolean usePmk, boolean acceptAny, boolean forceChannel) {
         super(context, isUnsolicited, false);
 
         mIsSecurityOpen = isSecurityOpen;
         mUsePmk = usePmk;
         mIsPublish = isPublish;
         mIsAcceptAny = acceptAny;
+        mForceChannel = forceChannel;
     }
 
     @Override
@@ -182,6 +187,15 @@
                 nsBuilder.setPskPassphrase(PASSPHRASE);
             }
         }
+        if (mForceChannel) {
+            nsBuilder.setChannelInMhz(CHANNEL_IN_MHZ, true);
+            WifiAwareNetworkSpecifier ns = nsBuilder.build();
+            if (ns.getChannelInMhz() != CHANNEL_IN_MHZ || !ns.isChannelRequired()) {
+                Log.e(TAG, "executeTestSubscriber: channel configure for data-path is not match");
+                return false;
+            }
+        }
+
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
                 nsBuilder.build()).build();
@@ -214,14 +228,40 @@
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_success));
         if (DBG) Log.d(TAG, "executeTestSubscriber: network request granted - AVAILABLE");
 
-        if (!mIsSecurityOpen) {
-            if (!(info.second.getTransportInfo() instanceof WifiAwareNetworkInfo)) {
+        if (!(info.second.getTransportInfo() instanceof WifiAwareNetworkInfo)) {
+            setFailureReason(mContext.getString(R.string.aware_status_network_failed));
+            Log.e(TAG, "executeTestSubscriber: did not get WifiAwareNetworkInfo from peer!?");
+            return false;
+        }
+        WifiAwareNetworkInfo peerAwareInfo =
+                (WifiAwareNetworkInfo) info.second.getTransportInfo();
+        StringBuilder builder = new StringBuilder();
+        for (WifiAwareChannelInfo channelInfo : peerAwareInfo.getChannelInfos()) {
+            builder.append(channelInfo.toString());
+        }
+        if (DBG) Log.d(TAG, "executeTestSubscriber: channelInfo:" + builder.toString());
+
+        if (mForceChannel) {
+            if (peerAwareInfo.getChannelInfos().size() != 1) {
                 setFailureReason(mContext.getString(R.string.aware_status_network_failed));
-                Log.e(TAG, "executeTestSubscriber: did not get WifiAwareNetworkInfo from peer!?");
+                Log.e(TAG, "executeTestSubscriber: number of channel info is incorrect");
                 return false;
             }
-            WifiAwareNetworkInfo peerAwareInfo =
-                    (WifiAwareNetworkInfo) info.second.getTransportInfo();
+            WifiAwareChannelInfo channelInfo = peerAwareInfo.getChannelInfos().get(0);
+            if (channelInfo.getChannelFreqInMhz() != CHANNEL_IN_MHZ) {
+                setFailureReason(mContext.getString(R.string.aware_status_network_failed));
+                Log.e(TAG, "executeTestSubscriber: channel freq is not match the request");
+                return false;
+            }
+            if (DBG) {
+                Log.d(TAG, "executeTestSubscriber: ChannelFreqInMhz="
+                        + channelInfo.getChannelFreqInMhz()
+                        + " ChannelBandWidth=" + channelInfo.getChannelBandwidth()
+                        + " NumSpatialStreams=" + channelInfo.getNumSpatialStreams());
+            }
+        }
+
+        if (!mIsSecurityOpen) {
             Inet6Address peerIpv6 = peerAwareInfo.getPeerIpv6Addr();
             int peerPort = peerAwareInfo.getPort();
             int peerTransportProtocol = peerAwareInfo.getTransportProtocol();
@@ -385,6 +425,14 @@
             }
             nsBuilder.setPort(port).setTransportProtocol(6); // 6 == TCP
         }
+        if (mForceChannel) {
+            nsBuilder.setChannelInMhz(CHANNEL_IN_MHZ, true);
+            WifiAwareNetworkSpecifier ns = nsBuilder.build();
+            if (ns.getChannelInMhz() != CHANNEL_IN_MHZ || !ns.isChannelRequired()) {
+                Log.e(TAG, "executeTestSubscriber: channel configure for data-path is not match");
+                return false;
+            }
+        }
         NetworkRequest nr = new NetworkRequest.Builder().addTransportType(
                 NetworkCapabilities.TRANSPORT_WIFI_AWARE).setNetworkSpecifier(
                 nsBuilder.build()).build();
diff --git a/apps/TileServiceApp/Android.bp b/apps/TileServiceApp/Android.bp
new file mode 100644
index 0000000..55038f3
--- /dev/null
+++ b/apps/TileServiceApp/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsTileServiceApp",
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    min_sdk_version: "31",
+}
diff --git a/apps/TileServiceApp/AndroidManifest.xml b/apps/TileServiceApp/AndroidManifest.xml
new file mode 100644
index 0000000..be693b1
--- /dev/null
+++ b/apps/TileServiceApp/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.tileserviceapp">
+
+    <uses-sdk android:minSdkVersion="31"/>
+
+    <application android:label="CtsTileServiceApp"
+         android:icon="@android:drawable/ic_dialog_alert">
+        <activity android:name=".TileRequestActivity"
+             android:exported="true">
+        </activity>
+
+        <service android:name=".RequestTileService"
+                 android:icon="@android:drawable/ic_dialog_alert"
+                 android:label="@string/tile_request_service_name"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/apps/TileServiceApp/OWNERS b/apps/TileServiceApp/OWNERS
new file mode 100644
index 0000000..3ae17a9
--- /dev/null
+++ b/apps/TileServiceApp/OWNERS
@@ -0,0 +1 @@
+include /apps/CtsVerifier/src/com/android/cts/verifier/qstiles/OWNERS
\ No newline at end of file
diff --git a/apps/TileServiceApp/res/values/strings.xml b/apps/TileServiceApp/res/values/strings.xml
new file mode 100755
index 0000000..79ef54e
--- /dev/null
+++ b/apps/TileServiceApp/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="tile_request_service_name">Request Tile Service</string>
+</resources>
diff --git a/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java
new file mode 100644
index 0000000..6000c7b
--- /dev/null
+++ b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/RequestTileService.java
@@ -0,0 +1,23 @@
+/*
+ * 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.tileserviceapp;
+
+import android.service.quicksettings.TileService;
+
+/** TileService for test */
+public class RequestTileService extends TileService {
+}
diff --git a/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java
new file mode 100644
index 0000000..08f7806
--- /dev/null
+++ b/apps/TileServiceApp/src/com/android/cts/tileserviceapp/TileRequestActivity.java
@@ -0,0 +1,65 @@
+/*
+ * 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.tileserviceapp;
+
+import android.app.Activity;
+import android.app.StatusBarManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * A simple activity that requests permissions and returns the result.
+ */
+public final class TileRequestActivity extends Activity {
+
+    private static final String TAG = "TileRequestActivity";
+    private String mTileLabel;
+    private StatusBarManager mStatusBarManager;
+    private Icon mIcon;
+
+    private ComponentName getTileComponentName() {
+        return new ComponentName(this, RequestTileService.class);
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mTileLabel = getString(R.string.tile_request_service_name);
+        mStatusBarManager = getSystemService(StatusBarManager.class);
+        mIcon = Icon.createWithResource(this, android.R.drawable.ic_dialog_alert);
+
+        final Intent received = getIntent();
+        Log.d(TAG, "Started with " + received);
+        requestTile();
+    }
+
+    private void requestTile() {
+        mStatusBarManager.requestAddTileService(
+                getTileComponentName(),
+                mTileLabel,
+                mIcon,
+                getMainExecutor(),
+                integer -> {
+                    setResult(integer);
+                    finish();
+                }
+        );
+    }
+}
diff --git a/apps/VpnApp/Android.bp b/apps/VpnApp/Android.bp
index 898f4bd..a6ee6e4 100644
--- a/apps/VpnApp/Android.bp
+++ b/apps/VpnApp/Android.bp
@@ -28,6 +28,7 @@
     defaults: ["CtsVpnAppDefaults"],
     manifest: "api23/AndroidManifest.xml",
     test_suites: [
+        "arcts",
         "cts",
         "general-tests",
     ],
@@ -38,6 +39,7 @@
     defaults: ["CtsVpnAppDefaults"],
     manifest: "api24/AndroidManifest.xml",
     test_suites: [
+        "arcts",
         "cts",
         "general-tests",
     ],
@@ -48,6 +50,7 @@
     defaults: ["CtsVpnAppDefaults"],
     manifest: "latest/AndroidManifest.xml",
     test_suites: [
+        "arcts",
         "cts",
         "general-tests",
     ],
@@ -58,6 +61,7 @@
     defaults: ["CtsVpnAppDefaults"],
     manifest: "notalwayson/AndroidManifest.xml",
     test_suites: [
+        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml b/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
index 35b0796..7733f55 100644
--- a/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
+++ b/common/device-side/bedstead/deviceadminapp/src/main/AndroidManifest.xml
@@ -19,7 +19,7 @@
           package="com.android.bedstead.deviceadminapp"
           android:targetSandboxVersion="2">
 
-    <application android:testOnly="true">
+    <application>
         <receiver android:name="com.android.eventlib.premade.EventLibDeviceAdminReceiver"
                   android:permission="android.permission.BIND_DEVICE_ADMIN"
                   android:exported="true">
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
index b2d51f8..3f8b590 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/IpcBroadcastReceiver.java
@@ -32,6 +32,10 @@
    <pre><code>
    <receiver android:name="com.android.bedstead.dpmwrapper.IpcBroadcastReceiver"
              android:exported="true">
+     <intent-filter>
+       <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
+     </intent-filter>
+   </receiver>
    </code></pre>
  */
 // TODO(b/176993670): remove when DpmWrapper IPC mechanism changes
diff --git a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
index 0f33307..d96c0b1 100644
--- a/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
+++ b/common/device-side/bedstead/dpmwrapper/src/main/java/com/android/bedstead/dpmwrapper/TestAppSystemServiceFactory.java
@@ -317,7 +317,8 @@
                 case RESULT_NOT_SENT_TO_ANY_RECEIVER:
                     fail("Didn't receive result from ordered broadcast - did you override "
                             + receiverClassName + ".onReceive() to call "
-                            + "DeviceOwnerHelper.runManagerMethod()?");
+                            + "DeviceOwnerHelper.runManagerMethod()? Did you add "
+                            + ACTION_WRAPPED_MANAGER_CALL + " to its intent filter / manifest?");
                     return null;
                 default:
                     fail("Received invalid result for method %s: %s", methodName, result);
diff --git a/common/device-side/bedstead/harrier/Android.bp b/common/device-side/bedstead/harrier/Android.bp
index e3f6715..53c09da 100644
--- a/common/device-side/bedstead/harrier/Android.bp
+++ b/common/device-side/bedstead/harrier/Android.bp
@@ -16,6 +16,40 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_library_host {
+    name: "HarrierCommon",
+    srcs: [
+        "common/src/main/java/**/*.java",
+    ],
+
+    static_libs: [
+        "junit",
+        "auto_value_annotations",
+        "guava",
+        "NeneCommon"
+    ],
+
+    plugins: ["auto_annotation_plugin"]
+}
+
+android_library {
+    name: "HarrierCommonAndroid",
+    srcs: [
+        "common/src/main/java/**/*.java",
+    ],
+
+    static_libs: [
+        "junit",
+        "auto_value_annotations",
+        "guava",
+        "NeneCommonAndroid"
+    ],
+
+    manifest: "src/main/AndroidManifest.xml",
+    min_sdk_version: "27",
+    plugins: ["auto_annotation_plugin"]
+}
+
 android_library {
     name: "Harrier",
     sdk_version: "test_current",
@@ -27,6 +61,7 @@
     static_libs: [
         "Nene",
         "RemoteDPC",
+        "HarrierCommonAndroid",
         "compatibility-device-util-axt",
         "androidx.test.ext.junit",
         "auto_value_annotations"
@@ -34,7 +69,6 @@
 
     manifest: "src/main/AndroidManifest.xml",
     min_sdk_version: "27",
-    plugins: ["auto_annotation_plugin"],
 }
 
 android_test {
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java
new file mode 100644
index 0000000..77bb8d1
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadFrameworkMethod.java
@@ -0,0 +1,111 @@
+/*
+ * 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.harrier;
+
+import com.google.common.base.Objects;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nullable;
+
+/**
+ * {@link FrameworkMethod} subclass which allows modifying the test name and annotations.
+ */
+public final class BedsteadFrameworkMethod extends FrameworkMethod {
+
+    private final Annotation mParameterizedAnnotation;
+    private final Map<Class<? extends Annotation>, Annotation> mAnnotationsMap =
+            new HashMap<>();
+    private Annotation[] mAnnotations;
+
+    public BedsteadFrameworkMethod(Method method) {
+        this(method, /* parameterizedAnnotation= */ null);
+    }
+
+    public BedsteadFrameworkMethod(Method method,
+            @Nullable Annotation parameterizedAnnotation) {
+        super(method);
+        mParameterizedAnnotation = parameterizedAnnotation;
+
+        calculateAnnotations();
+    }
+
+    private void calculateAnnotations() {
+        List<Annotation> annotations =
+                new ArrayList<>(Arrays.asList(getDeclaringClass().getAnnotations()));
+        annotations.sort(BedsteadJUnit4::annotationSorter);
+
+        annotations.addAll(Arrays.stream(getMethod().getAnnotations())
+                .sorted(BedsteadJUnit4::annotationSorter)
+                .collect(Collectors.toList()));
+
+        BedsteadJUnit4.parseEnterpriseAnnotations(annotations);
+
+        BedsteadJUnit4.resolveRecursiveAnnotations(annotations, mParameterizedAnnotation);
+
+        mAnnotations = annotations.toArray(new Annotation[0]);
+        for (Annotation annotation : annotations) {
+            if (annotation instanceof DynamicParameterizedAnnotation) {
+                continue; // don't return this
+            }
+            mAnnotationsMap.put(annotation.annotationType(), annotation);
+        }
+    }
+
+    @Override
+    public String getName() {
+        if (mParameterizedAnnotation == null) {
+            return super.getName();
+        }
+        return super.getName() + "[" + BedsteadJUnit4.getParameterName(mParameterizedAnnotation)
+                + "]";
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (!super.equals(obj)) {
+            return false;
+        }
+
+        if (!(obj instanceof BedsteadFrameworkMethod)) {
+            return false;
+        }
+
+        BedsteadFrameworkMethod other = (BedsteadFrameworkMethod) obj;
+
+        return Objects.equal(mParameterizedAnnotation, other.mParameterizedAnnotation);
+    }
+
+    @Override
+    public Annotation[] getAnnotations() {
+        return mAnnotations;
+    }
+
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+        return (T) mAnnotationsMap.get(annotationType);
+    }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
new file mode 100644
index 0000000..646291a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
@@ -0,0 +1,501 @@
+/*
+ * 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.harrier;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
+import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
+import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
+import com.android.bedstead.nene.annotations.Nullable;
+import com.android.bedstead.nene.exceptions.NeneException;
+
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * A JUnit test runner for use with Bedstead.
+ */
+public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner {
+
+    private static final String BEDSTEAD_PACKAGE_NAME = "com.android.bedstead";
+
+    // These are annotations which are not included indirectly
+    private static final Set<String> sIgnoredAnnotationPackages = new HashSet<>();
+
+    static {
+        sIgnoredAnnotationPackages.add("java.lang.annotation");
+        sIgnoredAnnotationPackages.add("com.android.bedstead.harrier.annotations.meta");
+        sIgnoredAnnotationPackages.add("kotlin.*");
+        sIgnoredAnnotationPackages.add("org.junit");
+    }
+
+    static int annotationSorter(Annotation a, Annotation b) {
+        return getAnnotationWeight(a) - getAnnotationWeight(b);
+    }
+
+    private static int getAnnotationWeight(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            // Special case, not important
+            return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
+        }
+
+        if (!annotation.annotationType().getPackage().getName().startsWith(BEDSTEAD_PACKAGE_NAME)) {
+            return AnnotationRunPrecedence.FIRST;
+        }
+
+        try {
+            return (int) annotation.annotationType().getMethod("weight").invoke(annotation);
+        } catch (NoSuchMethodException e) {
+            // Default to PRECEDENCE_NOT_IMPORTANT if no weight is found on the annotation.
+            return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            throw new NeneException("Failed to invoke weight on this annotation: " + annotation, e);
+        }
+    }
+
+    static String getParameterName(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return ((DynamicParameterizedAnnotation) annotation).name();
+        }
+        return annotation.annotationType().getSimpleName();
+    }
+
+    /**
+     * Resolve annotations recursively.
+     *
+     * @param parameterizedAnnotation The class of the parameterized annotation to expand, if any
+     */
+    public static void resolveRecursiveAnnotations(List<Annotation> annotations,
+            @Nullable Annotation parameterizedAnnotation) {
+        int index = 0;
+        while (index < annotations.size()) {
+            Annotation annotation = annotations.get(index);
+            annotations.remove(index);
+            List<Annotation> replacementAnnotations =
+                    getReplacementAnnotations(annotation, parameterizedAnnotation);
+            replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+            annotations.addAll(index, replacementAnnotations);
+            index += replacementAnnotations.size();
+        }
+    }
+
+    private static boolean isParameterizedAnnotation(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return true;
+        }
+
+        return annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
+    }
+
+    private static Annotation[] getIndirectAnnotations(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return ((DynamicParameterizedAnnotation) annotation).annotations();
+        }
+        return annotation.annotationType().getAnnotations();
+    }
+
+    private static boolean isRepeatingAnnotation(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return false;
+        }
+
+        return annotation.annotationType().getAnnotation(RepeatingAnnotation.class) != null;
+    }
+
+    private static List<Annotation> getReplacementAnnotations(Annotation annotation,
+            @Nullable Annotation parameterizedAnnotation) {
+        List<Annotation> replacementAnnotations = new ArrayList<>();
+
+        if (isRepeatingAnnotation(annotation)) {
+            try {
+                Annotation[] annotations =
+                        (Annotation[]) annotation.annotationType()
+                                .getMethod("value").invoke(annotation);
+                Collections.addAll(replacementAnnotations, annotations);
+                return replacementAnnotations;
+            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+                throw new NeneException("Error expanding repeated annotations", e);
+            }
+        }
+
+        if (isParameterizedAnnotation(annotation) && !annotation.equals(parameterizedAnnotation)) {
+            return replacementAnnotations;
+        }
+
+        for (Annotation indirectAnnotation : getIndirectAnnotations(annotation)) {
+            if (shouldSkipAnnotation(annotation)) {
+                continue;
+            }
+
+            replacementAnnotations.addAll(getReplacementAnnotations(
+                    indirectAnnotation, parameterizedAnnotation));
+        }
+
+        if (!(annotation instanceof DynamicParameterizedAnnotation)) {
+            // We drop the fake annotation once it's replaced
+            replacementAnnotations.add(annotation);
+        }
+
+        return replacementAnnotations;
+    }
+
+    private static boolean shouldSkipAnnotation(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return false;
+        }
+
+        String annotationPackage = annotation.annotationType().getPackage().getName();
+
+        for (String ignoredPackage : sIgnoredAnnotationPackages) {
+            if (ignoredPackage.endsWith(".*")) {
+                if (annotationPackage.startsWith(
+                        ignoredPackage.substring(0, ignoredPackage.length() - 2))) {
+                    return true;
+                }
+            } else if (annotationPackage.equals(ignoredPackage)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    public BedsteadJUnit4(Class<?> testClass) throws InitializationError {
+        super(testClass);
+    }
+
+    private boolean annotationShouldBeSkipped(Annotation annotation) {
+        if (annotation instanceof DynamicParameterizedAnnotation) {
+            return false;
+        }
+
+        return annotation.annotationType().equals(IncludeNone.class);
+    }
+
+    @Override
+    protected List<FrameworkMethod> computeTestMethods() {
+        TestClass testClass = getTestClass();
+
+        List<FrameworkMethod> basicTests = testClass.getAnnotatedMethods(Test.class);
+        List<FrameworkMethod> modifiedTests = new ArrayList<>();
+
+        for (FrameworkMethod m : basicTests) {
+            Set<Annotation> parameterizedAnnotations = getParameterizedAnnotations(m);
+
+            if (parameterizedAnnotations.isEmpty()) {
+                // Unparameterized, just add the original
+                modifiedTests.add(new BedsteadFrameworkMethod(m.getMethod()));
+            }
+
+            for (Annotation annotation : parameterizedAnnotations) {
+                if (annotationShouldBeSkipped(annotation)) {
+                    // Special case - does not generate a run
+                    continue;
+                }
+                modifiedTests.add(
+                        new BedsteadFrameworkMethod(m.getMethod(), annotation));
+            }
+        }
+
+        modifiedTests = generateGeneralParameterisationMethods(modifiedTests);
+
+        sortMethodsByBedsteadAnnotations(modifiedTests);
+
+        return modifiedTests;
+    }
+
+    private static List<FrameworkMethod> generateGeneralParameterisationMethods(
+            List<FrameworkMethod> modifiedTests) {
+        return modifiedTests.stream()
+                .flatMap(BedsteadJUnit4::generateGeneralParameterisationMethods)
+                .collect(Collectors.toList());
+    }
+
+    private static Stream<FrameworkMethod> generateGeneralParameterisationMethods(
+            FrameworkMethod method) {
+        Stream<FrameworkMethod> expandedMethods = Stream.of(method);
+        if (method.getMethod().getParameterCount() == 0) {
+            return expandedMethods;
+        }
+
+        for (Parameter parameter : method.getMethod().getParameters()) {
+            List<Annotation> annotations = new ArrayList<>(
+                    Arrays.asList(parameter.getAnnotations()));
+            resolveRecursiveAnnotations(annotations, /* parameterizedAnnotation= */ null);
+
+            boolean hasParameterised = false;
+
+            for (Annotation annotation : annotations) {
+                if (annotation instanceof StringTestParameter) {
+                    if (hasParameterised) {
+                        throw new IllegalStateException(
+                                "Each parameter can only have a single parameterised annotation");
+                    }
+                    hasParameterised = true;
+
+                    StringTestParameter stringTestParameter = (StringTestParameter) annotation;
+
+                    expandedMethods = expandedMethods.flatMap(
+                            i -> applyStringTestParameter(i, stringTestParameter));
+                } else if (annotation instanceof IntTestParameter) {
+                    if (hasParameterised) {
+                        throw new IllegalStateException(
+                                "Each parameter can only have a single parameterised annotation");
+                    }
+                    hasParameterised = true;
+
+                    IntTestParameter intTestParameter = (IntTestParameter) annotation;
+
+                    expandedMethods = expandedMethods.flatMap(
+                            i -> applyIntTestParameter(i, intTestParameter));
+                }
+            }
+
+            if (!hasParameterised) {
+                throw new IllegalStateException(
+                        "Parameter " + parameter + " must be annotated as parameterised");
+            }
+        }
+
+        return expandedMethods;
+    }
+
+    private static Stream<FrameworkMethod> applyStringTestParameter(FrameworkMethod frameworkMethod,
+            StringTestParameter stringTestParameter) {
+        return Stream.of(stringTestParameter.value()).map(
+                (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
+        );
+    }
+
+    private static Stream<FrameworkMethod> applyIntTestParameter(FrameworkMethod frameworkMethod,
+            IntTestParameter intTestParameter) {
+        return Arrays.stream(intTestParameter.value()).mapToObj(
+                (i) -> new FrameworkMethodWithParameter(frameworkMethod, i)
+        );
+    }
+
+    /**
+     * Sort methods so that methods with identical bedstead annotations are together.
+     *
+     * <p>This will also ensure that all tests methods which are not annotated for bedstead will
+     * run before any tests which are annotated.
+     */
+    private void sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests) {
+        List<Annotation> bedsteadAnnotationsSortedByMostCommon =
+                bedsteadAnnotationsSortedByMostCommon(modifiedTests);
+
+        modifiedTests.sort((o1, o2) -> {
+            for (Annotation annotation : bedsteadAnnotationsSortedByMostCommon) {
+                boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
+                boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
+
+                if (o1HasAnnotation && !o2HasAnnotation) {
+                    // o1 goes to the end
+                    return 1;
+                } else if (o2HasAnnotation && !o1HasAnnotation) {
+                    return -1;
+                }
+            }
+            return 0;
+        });
+    }
+
+    private List<Annotation> bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods) {
+        Map<Annotation, Integer> annotationCounts = countAnnotations(methods);
+        List<Annotation> annotations = new ArrayList<>(annotationCounts.keySet());
+
+        annotations.removeIf(
+                annotation ->
+                        !annotation.annotationType()
+                                .getCanonicalName().contains(BEDSTEAD_PACKAGE_NAME));
+
+        annotations.sort(Comparator.comparingInt(annotationCounts::get));
+        Collections.reverse(annotations);
+
+        return annotations;
+    }
+
+    private Map<Annotation, Integer> countAnnotations(List<FrameworkMethod> methods) {
+        Map<Annotation, Integer> annotationCounts = new HashMap<>();
+
+        for (FrameworkMethod method : methods) {
+            for (Annotation annotation : method.getAnnotations()) {
+                annotationCounts.put(
+                        annotation, annotationCounts.getOrDefault(annotation, 0) + 1);
+            }
+        }
+
+        return annotationCounts;
+    }
+
+    private Set<Annotation> getParameterizedAnnotations(FrameworkMethod method) {
+        Set<Annotation> parameterizedAnnotations = new HashSet<>();
+        List<Annotation> annotations = new ArrayList<>(Arrays.asList(method.getAnnotations()));
+
+        // TODO(scottjonathan): We're doing this twice... does it matter?
+        parseEnterpriseAnnotations(annotations);
+
+        for (Annotation annotation : annotations) {
+            if (isParameterizedAnnotation(annotation)) {
+                parameterizedAnnotations.add(annotation);
+            }
+        }
+
+        return parameterizedAnnotations;
+    }
+
+    /**
+     * Parse enterprise-specific annotations.
+     *
+     * <p>To be used before general annotation processing.
+     */
+    static void parseEnterpriseAnnotations(List<Annotation> annotations) {
+        int index = 0;
+        while (index < annotations.size()) {
+            Annotation annotation = annotations.get(index);
+            if (annotation instanceof PositivePolicyTest) {
+                annotations.remove(index);
+                Class<?> policy = ((PositivePolicyTest) annotation).policy();
+
+                EnterprisePolicy enterprisePolicy =
+                        policy.getAnnotation(EnterprisePolicy.class);
+                List<Annotation> replacementAnnotations =
+                        Policy.positiveStates(policy.getName(), enterprisePolicy);
+                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+                annotations.addAll(index, replacementAnnotations);
+                index += replacementAnnotations.size();
+            } else if (annotation instanceof NegativePolicyTest) {
+                annotations.remove(index);
+                Class<?> policy = ((NegativePolicyTest) annotation).policy();
+
+                EnterprisePolicy enterprisePolicy =
+                        policy.getAnnotation(EnterprisePolicy.class);
+                List<Annotation> replacementAnnotations =
+                        Policy.negativeStates(policy.getName(), enterprisePolicy);
+                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+                annotations.addAll(index, replacementAnnotations);
+                index += replacementAnnotations.size();
+            } else if (annotation instanceof CannotSetPolicyTest) {
+                annotations.remove(index);
+                Class<?> policy = ((CannotSetPolicyTest) annotation).policy();
+
+                EnterprisePolicy enterprisePolicy =
+                        policy.getAnnotation(EnterprisePolicy.class);
+                List<Annotation> replacementAnnotations =
+                        Policy.cannotSetPolicyStates(policy.getName(), enterprisePolicy,
+                                ((CannotSetPolicyTest) annotation).includeDeviceAdminStates(),
+                                ((CannotSetPolicyTest) annotation).includeNonDeviceAdminStates());
+                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+                annotations.addAll(index, replacementAnnotations);
+                index += replacementAnnotations.size();
+            } else if (annotation instanceof CanSetPolicyTest) {
+                annotations.remove(index);
+                Class<?> policy = ((CanSetPolicyTest) annotation).policy();
+                boolean singleTestOnly = ((CanSetPolicyTest) annotation).singleTestOnly();
+
+                EnterprisePolicy enterprisePolicy =
+                        policy.getAnnotation(EnterprisePolicy.class);
+                List<Annotation> replacementAnnotations =
+                        Policy.canSetPolicyStates(
+                                policy.getName(), enterprisePolicy, singleTestOnly);
+                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
+
+                annotations.addAll(index, replacementAnnotations);
+                index += replacementAnnotations.size();
+            } else {
+                index++;
+            }
+        }
+    }
+
+    @Override
+    protected List<TestRule> classRules() {
+        List<TestRule> rules = super.classRules();
+
+        for (TestRule rule : rules) {
+            if (rule instanceof HarrierRule) {
+                HarrierRule harrierRule = (HarrierRule) rule;
+
+                harrierRule.setSkipTestTeardown(true);
+                harrierRule.setUsingBedsteadJUnit4(true);
+
+                break;
+            }
+        }
+
+        return rules;
+    }
+
+    /**
+     * True if the test is running in debug mode.
+     *
+     * <p>This will result in additional debugging information being added which would otherwise
+     * be dropped to improve test performance.
+     *
+     * <p>To enable this, pass the "bedstead-debug" instrumentation arg as "true"
+     */
+    public static boolean isDebug() {
+        try {
+            Class instrumentationRegistryClass = Class.forName(
+                        "androidx.test.platform.app.InstrumentationRegistry");
+
+            Object arguments = instrumentationRegistryClass.getMethod("getArguments")
+                    .invoke(null);
+            return Boolean.parseBoolean((String) arguments.getClass()
+                    .getMethod("getString", String.class, String.class)
+                    .invoke(arguments, "bedstead-debug", "false"));
+        } catch (ClassNotFoundException e) {
+            return false; // Must be on the host so can't access debug information
+        } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+            throw new IllegalStateException("Error getting isDebug", e);
+        }
+    }
+
+    @Override
+    protected void validateTestMethods(List<Throwable> errors) {
+        // We do allow arguments - they will fail validation later on if not properly annotated
+    }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java
new file mode 100644
index 0000000..13320bc
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Defaults.java
@@ -0,0 +1,33 @@
+/*
+ * 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.harrier;
+
+/** Default values used across Harrier. */
+public final class Defaults {
+
+    private Defaults() {
+
+    }
+
+    /**
+     * The password to be used in tests.
+     *
+     * The infrastructure will use this password when a password exists and is required.
+     */
+    public static final String DEFAULT_PASSWORD = "1234";
+
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/DynamicParameterizedAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java
new file mode 100644
index 0000000..4056890
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/FrameworkMethodWithParameter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.harrier;
+
+import org.junit.runners.model.FrameworkMethod;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+
+/**
+ * A {@link FrameworkMethod} which forwards calls to a wrapped {@link FrameworkMethod} except
+ * that it injects a parameter and adds the parameter to the name.
+ */
+public final class FrameworkMethodWithParameter extends FrameworkMethod {
+
+    private final FrameworkMethod mWrappedFrameworkMethod;
+    private final Object mInjectedParam;
+
+    public FrameworkMethodWithParameter(FrameworkMethod frameworkMethod, Object injectedParam) {
+        super(frameworkMethod.getMethod());
+        mWrappedFrameworkMethod = frameworkMethod;
+        mInjectedParam = injectedParam;
+    }
+
+    @Override
+    public boolean isStatic() {
+        if (mWrappedFrameworkMethod == null) {
+            return super.isStatic();
+        }
+        return mWrappedFrameworkMethod.isStatic();
+    }
+
+    @Override
+    public boolean isPublic() {
+        if (mWrappedFrameworkMethod == null) {
+            return super.isPublic();
+        }
+        return mWrappedFrameworkMethod.isPublic();
+    }
+
+    @Override
+    public Method getMethod() {
+        return mWrappedFrameworkMethod.getMethod();
+    }
+
+    @Override
+    public Object invokeExplosively(Object target, Object... params) throws Throwable {
+        Object[] allParams = params;
+        if (mInjectedParam != null) {
+            allParams = new Object[1 + params.length];
+            allParams[0] = mInjectedParam;
+            System.arraycopy(params, 0, allParams, 1, params.length);
+        }
+
+        return mWrappedFrameworkMethod.invokeExplosively(target, allParams);
+    }
+
+    @Override
+    public String getName() {
+        if (mInjectedParam != null) {
+            return mWrappedFrameworkMethod.getName() + "[" + mInjectedParam + "]";
+        }
+        return mWrappedFrameworkMethod.getName();
+    }
+
+    @Override
+    public void validatePublicVoidNoArg(boolean isStatic, List<Throwable> errors) {
+        mWrappedFrameworkMethod.validatePublicVoidNoArg(isStatic, errors);
+    }
+
+    @Override
+    public void validatePublicVoid(boolean isStatic, List<Throwable> errors) {
+        mWrappedFrameworkMethod.validatePublicVoid(isStatic, errors);
+    }
+
+    @Override
+    public Class<?> getReturnType() {
+        return mWrappedFrameworkMethod.getReturnType();
+    }
+
+    @Override
+    public Class<?> getType() {
+        return mWrappedFrameworkMethod.getType();
+    }
+
+    @Override
+    public Class<?> getDeclaringClass() {
+        return mWrappedFrameworkMethod.getDeclaringClass();
+    }
+
+    @Override
+    public void validateNoTypeParametersOnArgs(List<Throwable> errors) {
+        mWrappedFrameworkMethod.validateNoTypeParametersOnArgs(errors);
+    }
+
+    @Override
+    public boolean isShadowedBy(FrameworkMethod other) {
+        return mWrappedFrameworkMethod.isShadowedBy(other);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return mWrappedFrameworkMethod.equals(obj);
+    }
+
+    @Override
+    public int hashCode() {
+        return mWrappedFrameworkMethod.hashCode();
+    }
+
+    @Override
+    public boolean producesType(Type type) {
+        return mWrappedFrameworkMethod.producesType(type);
+    }
+
+    @Override
+    public Annotation[] getAnnotations() {
+        return mWrappedFrameworkMethod.getAnnotations();
+    }
+
+    @Override
+    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
+        return mWrappedFrameworkMethod.getAnnotation(annotationType);
+    }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.java
new file mode 100644
index 0000000..35bc955
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/HarrierRule.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 com.android.bedstead.harrier;
+
+import org.junit.rules.TestRule;
+
+/** A @Rule used on device by Harrier. */
+public abstract class HarrierRule implements TestRule {
+    /** Set that we should skip tearing down between tests. */
+    abstract void setSkipTestTeardown(boolean skipTestTeardown);
+    /** Set that we are using the BedsteadJUnit4 test runner. */
+    abstract void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4);
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/OptionalBoolean.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java
new file mode 100644
index 0000000..d7ea1bd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/Policy.java
@@ -0,0 +1,531 @@
+/*
+ * 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.harrier;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_AFFILIATED_OTHER_USERS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_PARENT;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_UNAFFILIATED_OTHER_USERS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.DO_NOT_APPLY_TO_NEGATIVE_TESTS;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.NO;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_APP_RESTRICTIONS;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_BLOCK_UNINSTALL;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_CERT_INSTALL;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_CERT_SELECTION;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_ENABLE_SYSTEM_APP;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_INSTALL_EXISTING_PACKAGE;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_NETWORK_LOGGING;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PACKAGE_ACCESS;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_SECURITY_LOGGING;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedProfileOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfCorporateOwnedProfileOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerUsingParentInstance;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser;
+
+import com.google.auto.value.AutoAnnotation;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Utility class for enterprise policy tests.
+ */
+public final class Policy {
+
+    // Delegate scopes to be used for a "CannotSet" state. All delegate scopes except the ones which
+    // should allow use of the API will be granted
+    private static final ImmutableSet<String> ALL_DELEGATE_SCOPES = ImmutableSet.of(
+            DELEGATION_CERT_INSTALL,
+            DELEGATION_APP_RESTRICTIONS,
+            DELEGATION_BLOCK_UNINSTALL,
+            DELEGATION_PERMISSION_GRANT,
+            DELEGATION_PACKAGE_ACCESS,
+            DELEGATION_ENABLE_SYSTEM_APP,
+            DELEGATION_INSTALL_EXISTING_PACKAGE,
+            DELEGATION_KEEP_UNINSTALLED_PACKAGES,
+            DELEGATION_NETWORK_LOGGING,
+            DELEGATION_CERT_SELECTION,
+            DELEGATION_SECURITY_LOGGING
+    );
+
+    // This is a map containing all Include* annotations and the flags which lead to them
+    // This is not validated - every state must have a single APPLIED_BY annotation
+    private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+            STATE_ANNOTATIONS =
+            ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnDeviceOwnerUser()))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnDeviceOwnerUser(), /* isPrimary= */ true))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND, singleAnnotation(includeRunOnBackgroundDeviceOwnerUser()))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnBackgroundDeviceOwnerUser(), /* isPrimary= */ true))
+
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser()))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser()))
+                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
+
+                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
+                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
+                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerPrimaryUser(), /* isPrimary= */ true))
+
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner(), /* isPrimary= */ true))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT, singleAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner(), /* isPrimary= */ true))
+
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile(), /* isPrimary= */ true))
+
+                    .put(APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnParentOfProfileOwnerUsingParentInstance()))
+
+                    .build();
+    // This must contain one key for every APPLIED_BY that is being used, and maps to the
+    // "default" for testing that DPC type
+    // in general this will be a state which runs on the same user as the dpc.
+    private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+            DPC_STATE_ANNOTATIONS_BASE =
+            ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
+                    .put(APPLIED_BY_DEVICE_OWNER, (flags) -> hasFlag(flags.dpc(), APPLIED_BY_DEVICE_OWNER | APPLIES_IN_BACKGROUND) ? ImmutableSet.of(includeRunOnBackgroundDeviceOwnerUser()) : ImmutableSet.of(includeRunOnDeviceOwnerUser()))
+                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
+                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
+                    .build();
+    private static final Map<Integer, Function<EnterprisePolicy, Set<Annotation>>>
+            DPC_STATE_ANNOTATIONS = DPC_STATE_ANNOTATIONS_BASE.entrySet().stream()
+            .collect(Collectors.toMap(Map.Entry::getKey, Policy::addGeneratedStates));
+    private static final int APPLIED_BY_FLAGS =
+            APPLIED_BY_DEVICE_OWNER | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE
+                    | APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE
+                    | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER
+                    | APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER
+                    | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+    private static final Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>>
+            ANNOTATIONS_MAP = calculateAnnotationsMap(STATE_ANNOTATIONS);
+
+    private Policy() {
+
+    }
+
+    @AutoAnnotation
+    private static IncludeNone includeNone() {
+        return new AutoAnnotation_Policy_includeNone();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnDeviceOwnerUser includeRunOnDeviceOwnerUser() {
+        return new AutoAnnotation_Policy_includeRunOnDeviceOwnerUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser includeRunOnNonAffiliatedDeviceOwnerSecondaryUser() {
+        return new AutoAnnotation_Policy_includeRunOnNonAffiliatedDeviceOwnerSecondaryUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnAffiliatedDeviceOwnerSecondaryUser includeRunOnAffiliatedDeviceOwnerSecondaryUser() {
+        return new AutoAnnotation_Policy_includeRunOnAffiliatedDeviceOwnerSecondaryUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnAffiliatedProfileOwnerSecondaryUser includeRunOnAffiliatedProfileOwnerSecondaryUser() {
+        return new AutoAnnotation_Policy_includeRunOnAffiliatedProfileOwnerSecondaryUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser includeRunOnUnaffiliatedProfileOwnerSecondaryUser() {
+        return new AutoAnnotation_Policy_includeRunOnUnaffiliatedProfileOwnerSecondaryUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnProfileOwnerProfileWithNoDeviceOwner includeRunOnProfileOwnerProfileWithNoDeviceOwner() {
+        return new AutoAnnotation_Policy_includeRunOnProfileOwnerProfileWithNoDeviceOwner();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile() {
+        return new AutoAnnotation_Policy_includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner includeRunOnParentOfProfileOwnerWithNoDeviceOwner() {
+        return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerWithNoDeviceOwner();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnParentOfCorporateOwnedProfileOwner includeRunOnParentOfCorporateOwnedProfileOwner() {
+        return new AutoAnnotation_Policy_includeRunOnParentOfCorporateOwnedProfileOwner();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnProfileOwnerPrimaryUser includeRunOnProfileOwnerPrimaryUser() {
+        return new AutoAnnotation_Policy_includeRunOnProfileOwnerPrimaryUser();
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnBackgroundDeviceOwnerUser includeRunOnBackgroundDeviceOwnerUser() {
+        return new AutoAnnotation_Policy_includeRunOnBackgroundDeviceOwnerUser();
+    }
+
+    @AutoAnnotation
+    private static EnsureHasDelegate ensureHasDelegate(EnsureHasDelegate.AdminType admin,
+            String[] scopes, boolean isPrimary) {
+        return new AutoAnnotation_Policy_ensureHasDelegate(admin, scopes, isPrimary);
+    }
+
+    @AutoAnnotation
+    private static IncludeRunOnParentOfProfileOwnerUsingParentInstance includeRunOnParentOfProfileOwnerUsingParentInstance() {
+        return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerUsingParentInstance();
+    }
+
+    private static Function<EnterprisePolicy, Set<Annotation>> singleAnnotation(
+            Annotation annotation) {
+        return (i) -> ImmutableSet.of(annotation);
+    }
+
+    private static Function<EnterprisePolicy, Set<Annotation>> generateDelegateAnnotation(
+            Annotation annotation, boolean isPrimary) {
+        return (policy) -> {
+            Annotation[] existingAnnotations = annotation.annotationType().getAnnotations();
+            return Arrays.stream(policy.delegatedScopes())
+                    .map(scope -> {
+                        Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+                                existingAnnotations.length + 1);
+                        newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+                                EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
+                                isPrimary);
+
+                        return new DynamicParameterizedAnnotation(
+                                annotation.annotationType().getSimpleName() + "Delegate:" + scope,
+                                newAnnotations);
+                    }).collect(Collectors.toSet());
+        };
+    }
+
+    private static Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> calculateAnnotationsMap(
+            Map<Integer, Function<EnterprisePolicy, Set<Annotation>>> annotations) {
+        Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> b = new HashMap<>();
+
+        for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> i :
+                annotations.entrySet()) {
+            if (!b.containsKey(i.getValue())) {
+                b.put(i.getValue(), new HashSet<>());
+            }
+
+            b.get(i.getValue()).add(i.getKey());
+        }
+
+        return b;
+    }
+
+    private static Function<EnterprisePolicy, Set<Annotation>> addGeneratedStates(
+            ImmutableMap.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> entry) {
+        return (policy) -> {
+            if (hasFlag(policy.dpc(), entry.getKey() | CAN_BE_DELEGATED)) {
+                Set<Annotation> results = new HashSet<>(entry.getValue().apply(policy));
+                results.addAll(results.stream().flatMap(
+                        t -> generateDelegateAnnotation(t, /* isPrimary= */ true).apply(
+                                policy).stream())
+                        .collect(Collectors.toSet()));
+
+                return results;
+            }
+
+            return entry.getValue().apply(policy);
+        };
+    }
+
+
+    /**
+     * Get parameterized test runs for the given policy.
+     *
+     * <p>These are states which should be run where the policy is able to be applied.
+     */
+    public static List<Annotation> positiveStates(String policyName,
+            EnterprisePolicy enterprisePolicy) {
+        Set<Annotation> annotations = new HashSet<>();
+
+        validateFlags(policyName, enterprisePolicy.dpc());
+
+        for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
+                ANNOTATIONS_MAP.entrySet()) {
+            if (isPositive(enterprisePolicy.dpc(), annotation.getValue())) {
+                annotations.addAll(annotation.getKey().apply(enterprisePolicy));
+            }
+        }
+
+        if (annotations.isEmpty()) {
+            // Don't run the original test unparameterized
+            annotations.add(includeNone());
+        }
+
+        return new ArrayList<>(annotations);
+    }
+
+    private static boolean isPositive(int[] policyFlags, Set<Integer> annotationFlags) {
+        for (int annotationFlag : annotationFlags) {
+            if (hasFlag(policyFlags, annotationFlag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean isNegative(int[] policyFlags, Set<Integer> annotationFlags) {
+        for (int annotationFlag : annotationFlags) {
+            if (hasFlag(annotationFlag, DO_NOT_APPLY_TO_NEGATIVE_TESTS, /* nonMatchingFlag= */
+                    NO)) {
+                return false; // We don't support using this annotation for negative tests
+            }
+
+            int appliedByFlag = APPLIED_BY_FLAGS & annotationFlag;
+            int otherFlags = annotationFlag ^ appliedByFlag; // remove the appliedByFlag
+            if (hasFlag(policyFlags, /* matchingFlag= */ appliedByFlag, /* nonMatchingFlag= */
+                    otherFlags)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Get negative parameterized test runs for the given policy.
+     *
+     * <p>These are states which should be run where the policy is not able to be applied.
+     */
+    public static List<Annotation> negativeStates(String policyName,
+            EnterprisePolicy enterprisePolicy) {
+        Set<Annotation> annotations = new HashSet<>();
+
+        validateFlags(policyName, enterprisePolicy.dpc());
+
+        for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
+                ANNOTATIONS_MAP.entrySet()) {
+            if (isNegative(enterprisePolicy.dpc(), annotation.getValue())) {
+                annotations.addAll(annotation.getKey().apply(enterprisePolicy));
+            }
+        }
+
+        if (annotations.isEmpty()) {
+            // Don't run the original test unparameterized
+            annotations.add(includeNone());
+        }
+
+        return new ArrayList<>(annotations);
+    }
+
+    /**
+     * Get parameterized test runs where the policy cannot be set for the given policy.
+     */
+    public static List<Annotation> cannotSetPolicyStates(String policyName,
+            EnterprisePolicy enterprisePolicy, boolean includeDeviceAdminStates,
+            boolean includeNonDeviceAdminStates) {
+        Set<Annotation> annotations = new HashSet<>();
+
+        validateFlags(policyName, enterprisePolicy.dpc());
+
+        if (includeDeviceAdminStates) {
+            int allFlags = 0;
+            for (int p : enterprisePolicy.dpc()) {
+                allFlags = allFlags | p;
+            }
+
+            for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
+                    DPC_STATE_ANNOTATIONS.entrySet()) {
+                if ((appliedByFlag.getKey() & allFlags) == 0) {
+                    annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
+                }
+            }
+        }
+
+        if (includeNonDeviceAdminStates) {
+            Set<String> validScopes = ImmutableSet.copyOf(enterprisePolicy.delegatedScopes());
+            String[] scopes = ALL_DELEGATE_SCOPES.stream()
+                    .filter(i -> !validScopes.contains(i))
+                    .toArray(String[]::new);
+            Annotation[] existingAnnotations = IncludeRunOnDeviceOwnerUser.class.getAnnotations();
+
+            if (BedsteadJUnit4.isDebug()) {
+                // Add a non-DPC with no delegate scopes
+                Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+                        existingAnnotations.length + 1);
+                newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+                        EnsureHasDelegate.AdminType.PRIMARY, new String[]{},
+                        /* isPrimary= */ true);
+                annotations.add(
+                        new DynamicParameterizedAnnotation("DelegateWithNoScopes", newAnnotations));
+
+                for (String scope : scopes) {
+                    newAnnotations = Arrays.copyOf(existingAnnotations,
+                            existingAnnotations.length + 1);
+                    newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+                            EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
+                            /* isPrimary= */ true);
+                    annotations.add(
+                            new DynamicParameterizedAnnotation("DelegateWithScope:" + scope, newAnnotations));
+                }
+            } else {
+                Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
+                        existingAnnotations.length + 1);
+                newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
+                        EnsureHasDelegate.AdminType.PRIMARY, scopes, /* isPrimary= */ true);
+                annotations.add(
+                        new DynamicParameterizedAnnotation("DelegateWithoutValidScope", newAnnotations));
+            }
+        }
+
+        if (annotations.isEmpty()) {
+            // Don't run the original test unparameterized
+            annotations.add(includeNone());
+        }
+
+        return new ArrayList<>(annotations);
+    }
+
+    /**
+     * Get state annotations where the policy can be set for the given policy.
+     */
+    public static List<Annotation> canSetPolicyStates(
+            String policyName, EnterprisePolicy enterprisePolicy, boolean singleTestOnly) {
+        Set<Annotation> annotations = new HashSet<>();
+
+        validateFlags(policyName, enterprisePolicy.dpc());
+
+        int allFlags = 0;
+        for (int p : enterprisePolicy.dpc()) {
+            allFlags = allFlags | p;
+        }
+
+        for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
+                DPC_STATE_ANNOTATIONS.entrySet()) {
+            if ((appliedByFlag.getKey() & allFlags) == appliedByFlag.getKey()) {
+                annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
+            }
+        }
+
+        if (annotations.isEmpty()) {
+            // Don't run the original test unparameterized
+            annotations.add(includeNone());
+        }
+
+        List<Annotation> annotationList = new ArrayList<>(annotations);
+
+        if (singleTestOnly) {
+            // We select one annotation in an arbitrary but deterministic way
+            annotationList.sort(Comparator.comparing(
+                    a -> a instanceof DynamicParameterizedAnnotation
+                            ? "DynamicParameterizedAnnotation" : a.annotationType().getName()));
+
+            // We don't want a delegate to be the representative test
+            Annotation firstAnnotation = annotationList.stream()
+                    .filter(i -> !(i instanceof  DynamicParameterizedAnnotation))
+                    .findFirst().get();
+            annotationList.clear();
+            annotationList.add(firstAnnotation);
+        }
+
+        return annotationList;
+    }
+
+    private static void validateFlags(String policyName, int[] values) {
+        int usedAppliedByFlags = 0;
+
+        for (int value : values) {
+            validateFlags(policyName, value);
+            int newUsedAppliedByFlags = usedAppliedByFlags | (value & APPLIED_BY_FLAGS);
+            if (newUsedAppliedByFlags == usedAppliedByFlags) {
+                throw new IllegalStateException(
+                        "Cannot have more than one policy flag APPLIED by the same component. "
+                                + "Error in policy " + policyName);
+            }
+            usedAppliedByFlags = newUsedAppliedByFlags;
+        }
+    }
+
+    private static void validateFlags(String policyName, int value) {
+        int matchingAppliedByFlags = APPLIED_BY_FLAGS & value;
+
+        if (matchingAppliedByFlags == 0) {
+            throw new IllegalStateException(
+                    "All policy flags must specify 1 APPLIED_BY flag. Policy " + policyName
+                            + " did not.");
+        }
+    }
+
+    private static boolean hasFlag(int[] values, int matchingFlag) {
+        return hasFlag(values, matchingFlag, /* nonMatchingFlag= */ NO);
+    }
+
+    private static boolean hasFlag(int[] values, int matchingFlag, int nonMatchingFlag) {
+        for (int value : values) {
+            if (hasFlag(value, matchingFlag, nonMatchingFlag)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasFlag(int value, int matchingFlag, int nonMatchingFlag) {
+        if (!((value & matchingFlag) == matchingFlag)) {
+            return false;
+        }
+
+        if (nonMatchingFlag != NO) {
+            return (value & nonMatchingFlag) != nonMatchingFlag;
+        }
+
+        return true;
+    }
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.java
new file mode 100644
index 0000000..9dacdae
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/UserType.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 com.android.bedstead.harrier;
+
+public enum UserType {
+    /** Only to be used with annotations. */
+    ANY,
+    SYSTEM_USER,
+    CURRENT_USER,
+    PRIMARY_USER,
+    SECONDARY_USER,
+    WORK_PROFILE,
+    TV_PROFILE,
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
new file mode 100644
index 0000000..3bb6449
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.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.bedstead.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Replacement for {@link org.junit.AfterClass} for use by classes which use {@code Devicestate}.
+ *
+ * <p>Methods annotated {@link AfterClass} must be public, static, must return {@code void}, and
+ * must take no arguments.
+ *
+ * <p>The annotated method will be called after all tests, once per class.
+ *
+ * <p>The state prior to calling this method is not guaranteed, as test methods may have changed the
+ * state. If any class-level annotation assumptions are violated this method will not be run.
+ *
+ * <p>If there are multiple methods annotated {@code @AfterClass} there is no guarantee as to the
+ * order they will be executed.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface AfterClass {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/AnnotationRunPrecedence.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
new file mode 100644
index 0000000..f6de30a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
@@ -0,0 +1,40 @@
+/*
+ * 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.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Replacement for {@link org.junit.BeforeClass} for use by classes which use {@code Devicestate}.
+ *
+ * <p>Methods annotated {@link BeforeClass} must be public, static, must return {@code void}, and
+ * must take no arguments.
+ *
+ * <p>The annotated method will be called before any tests, once per class. Class-level Bedstead
+ * annotations will be processed before running the method, and if any assumptions are violated the
+ * method will not be run.
+ *
+ * <p>If there are multiple methods annotated {@code @BeforeClass} there is no guarantee as to the
+ * order they will be executed.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface BeforeClass {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureDoesNotHavePermission.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
new file mode 100644
index 0000000..7e65661
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
@@ -0,0 +1,51 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has no secondary user that is not the
+ * instrumented user.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which
+ * has no secondary user that is not the current user. Otherwise, you can use {@code Devicestate}
+ * to ensure that the device enters the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface EnsureHasNoSecondaryUser {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
new file mode 100644
index 0000000..d4b1c94
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
@@ -0,0 +1,55 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has no Tv profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * no Tv profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoProfileAnnotation("com.android.tv.profile")
+public @interface EnsureHasNoTvProfile {
+    /** Which user type the tv profile should not be attached to. */
+    UserType forUser() default CURRENT_USER;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
new file mode 100644
index 0000000..d3693dd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.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.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which does not have a work profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * no work profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoProfileAnnotation("android.os.usertype.profile.MANAGED")
+@RequireFeature("android.software.managed_users")
+public @interface EnsureHasNoWorkProfile {
+    /** Which user type the work profile should not be attached to. */
+    UserType forUser() default CURRENT_USER;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasPermission.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
new file mode 100644
index 0000000..6113175
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.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 com.android.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a device which has a secondary user.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which
+ * has a secondary user that is not the current user. Otherwise, you can use {@code Devicestate}
+ * to ensure that the device enters the correct state for the method. If there is not already a
+ * secondary user on the device, and the device does not support creating additional users, then
+ * the test will be skipped.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface EnsureHasSecondaryUser {
+    /** Whether the instrumented test app should be installed in the secondary user. */
+    OptionalBoolean installInstrumentedApp() default TRUE;
+
+    /**
+     * Should we ensure that we are switched to the given user
+     */
+    OptionalBoolean switchedToUser() default ANY;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
new file mode 100644
index 0000000..aa4fbfe
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.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.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a Tv profile.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a Tv profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasProfileAnnotation("com.android.tv.profile")
+public @interface EnsureHasTvProfile {
+    /** Which user type the tv profile should be attached to. */
+    UserType forUser() default CURRENT_USER;
+
+    /** Whether the instrumented test app should be installed in the tv profile. */
+    OptionalBoolean installInstrumentedApp() default TRUE;
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default ANY;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
new file mode 100644
index 0000000..99badd8
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.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.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a user which has a work profile.
+ *
+ * <p>Use of this annotation implies
+ * {@code RequireFeature("android.software.managed_users", SKIP)}.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which has
+ * a work profile. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
+@RequireFeature("android.software.managed_users")
+@EnsureHasNoDeviceOwner // TODO: This should only apply on Android R+
+public @interface EnsureHasWorkProfile {
+    /** Which user type the work profile should be attached to. */
+    UserType forUser() default CURRENT_USER;
+
+    /** Whether the instrumented test app should be installed in the work profile. */
+    OptionalBoolean installInstrumentedApp() default TRUE;
+
+    /**
+     * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+     *
+     * <p>Only one device policy controller per test should be marked as primary.
+     */
+    boolean dpcIsPrimary() default false;
+
+    /**
+     * If true, uses the {@code DevicePolicyManager#getParentProfileInstance(ComponentName)}
+     * instance of the dpc when calling to .dpc()
+     *
+     * <p>Only used if {@link #dpcIsPrimary()} is true.
+     */
+    boolean useParentInstanceOfDpc() default false;
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default ANY;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
new file mode 100644
index 0000000..c87abb8
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
@@ -0,0 +1,57 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is not installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices with the package by
+ * using {@code DeviceState}.
+ *
+ * <p>Tests annotated with this will attempt to remove the package if it exists, or otherwise fail.
+ * If you'd rather skip or fail tests immediately without attempting to remove see
+ * {@link RequirePackageNotInstalled}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(EnsurePackagesNotInstalled.class)
+public @interface EnsurePackageNotInstalled {
+    String value();
+    UserType onUser() default UserType.CURRENT_USER;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackagesNotInstalled.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java
new file mode 100644
index 0000000..fe2dd5e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordNotSet.java
@@ -0,0 +1,53 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires no password be set on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsurePasswordNotSet {
+
+    /** The user who must not have a password. */
+    UserType forUser() default CURRENT_USER;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java
new file mode 100644
index 0000000..da2a09d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsurePasswordSet.java
@@ -0,0 +1,57 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires a password be set on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsurePasswordSet {
+
+    /** The user who must have a password. */
+    UserType forUser() default CURRENT_USER;
+
+    /** The password to set. Defaults to {@code Devicestate#DEFAULT_PASSWORD}. */
+    String password() default DEFAULT_PASSWORD;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java
new file mode 100644
index 0000000..10eb715
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/EnsureScreenIsOn.java
@@ -0,0 +1,47 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method requires the screen to be on.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureScreenIsOn {
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default MIDDLE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/FailureMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java
new file mode 100644
index 0000000..dca3d37
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/IntTestParameter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.annotations;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark an {@code int} parameter as being parameterised with the given values.
+ *
+ * <p>You must be using the {@link BedsteadJUnit4} test runner to use this annotation.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IntTestParameter {
+    int[] value();
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/NotificationsTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/Postsubmit.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
new file mode 100644
index 0000000..6c9c008
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
@@ -0,0 +1,52 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequirePackageNotInstalled(value = GMS_CORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageNotInstalled(value = PLAY_STORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageNotInstalled(value = GSF_PACKAGE, onUser = UserType.ANY)
+public @interface RequireAospBuild {
+    String GMS_CORE_PACKAGE = "com.google.android.gms";
+    String PLAY_STORE_PACKAGE = "com.android.vending";
+    String GSF_PACKAGE = "com.google.android.gsf";
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireCnGmsBuild.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeature.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
new file mode 100644
index 0000000..2fc40f6
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RepeatingAnnotation
+public @interface RequireDoesNotHaveFeatures {
+    RequireDoesNotHaveFeature[] value();
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeature.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireFeatures.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
new file mode 100644
index 0000000..86323c0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
@@ -0,0 +1,48 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
+import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequirePackageInstalled(value = GMS_CORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageInstalled(value = PLAY_STORE_PACKAGE, onUser = UserType.ANY)
+@RequirePackageInstalled(value = GSF_PACKAGE, onUser = UserType.ANY)
+public @interface RequireGmsBuild {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireHeadlessSystemUserMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
new file mode 100644
index 0000000..5fb415f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate that a test requires a low ram device.
+ *
+ * <p>This can be enforced by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireLowRamDevice {
+    String reason();
+    FailureMode failureMode() default FailureMode.SKIP;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotCnGmsBuild.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotHeadlessSystemUserMode.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
new file mode 100644
index 0000000..5041014
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to indicate that a test does not run on low ram devices.
+ *
+ * <p>This can be enforced by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireNotLowRamDevice {
+    String reason();
+    FailureMode failureMode() default FailureMode.SKIP;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
new file mode 100644
index 0000000..7cd5717
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
@@ -0,0 +1,57 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices lacking the package by
+ * using {@code DeviceState}.
+ *
+ * <p>By default the test will be skipped if the package is not available. If you'd rather the test
+ * fail then use {@code failureMode = FailureMode.FAIL}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequirePackagesInstalled.class)
+public @interface RequirePackageInstalled {
+    String value();
+    UserType onUser() default UserType.CURRENT_USER;
+    FailureMode failureMode() default FailureMode.SKIP;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
new file mode 100644
index 0000000..747eb5d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
@@ -0,0 +1,58 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run only when a given package is not installed.
+ *
+ * <p>You can guarantee that these methods do not run on devices with the package by
+ * using {@code DeviceState}.
+ *
+ * <p>By default the test will be skipped if the package is available. If you'd rather the test
+ * fail then use {@code failureMode = FailureMode.FAIL}. If you'd like to uninstall the package if
+ * it is installed, see {@link EnsurePackageNotInstalled}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequirePackagesNotInstalled.class)
+public @interface RequirePackageNotInstalled {
+    String value();
+    UserType onUser() default UserType.CURRENT_USER;
+    FailureMode failureMode() default FailureMode.SKIP;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesInstalled.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequirePackagesNotInstalled.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
new file mode 100644
index 0000000..5e84335
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a non-secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a non-secondary user
+ * is created and the test is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run on a secondary user by
+ * using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
+ * requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+// To ensure that the test doesn't run on the secondary user we require that the test run on
+// the primary user to ensure consistent behaviour.
+@RequireRunOnPrimaryUser
+public @interface RequireRunNotOnSecondaryUser {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
new file mode 100644
index 0000000..3f937af
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
@@ -0,0 +1,62 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the primary user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the primary user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the primary
+ * user by using {@code Devicestate}.
+ *
+ * <p>Note that in practice this requires that the test runs on the system user, but excludes
+ * headless system users. To mark that a test should run on the system user, including headless
+ * system users, see {@link RequireRunOnSystemUser}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation("android.os.usertype.full.SYSTEM")
+public @interface RequireRunOnPrimaryUser {
+    /**
+     * Should we ensure that we are switched to the given user
+     */
+    OptionalBoolean switchedToUser() default TRUE;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
new file mode 100644
index 0000000..08734ea
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.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 com.android.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on a secondary user.
+ *
+ * <p>Your test configuration should be such that this test is only run where a secondary user is
+ * created and the test is being run on that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
+ * using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
+ * requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation("android.os.usertype.full.SECONDARY")
+public @interface RequireRunOnSecondaryUser {
+    /**
+     * Should we ensure that we are switched to the given user
+     */
+    OptionalBoolean switchedToUser() default TRUE;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
new file mode 100644
index 0000000..03321ce
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.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 com.android.bedstead.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run on the system user.
+ *
+ * <p>Your test configuration should be such that this test is only run on the system user
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of the system
+ * user by using {@code Devicestate}.
+ *
+ * <p>Note that this requires that the test runs on the system user, including headless system
+ * users. To mark that a test should run on the primary user, excluding headless
+ * system users, see {@link RequireRunOnPrimaryUser}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnUserAnnotation(
+        {"android.os.usertype.full.SYSTEM", "android.os.usertype.system.HEADLESS"})
+public @interface RequireRunOnSystemUser {
+    /**
+     * Should we ensure that we are switched to the given user
+     */
+    OptionalBoolean switchedToUser() default TRUE;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
new file mode 100644
index 0000000..b5ab38c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
@@ -0,0 +1,62 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a Tv profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a Tv profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a Tv
+ * profile by using {@code Devicestate}.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnProfileAnnotation("com.android.tv.profile")
+public @interface RequireRunOnTvProfile {
+    OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default TRUE;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
new file mode 100644
index 0000000..b811fff
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
@@ -0,0 +1,82 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
+import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should run within a work profile.
+ *
+ * <p>Your test configuration should be such that this test is only run where a work profile is
+ * created and the test is being run within that user.
+ *
+ * <p>Optionally, you can guarantee that these methods do not run outside of a work
+ * profile by using {@code Devicestate}.
+ *
+ * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
+ * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit requirements.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireRunOnProfileAnnotation(value = "android.os.usertype.profile.MANAGED",
+        hasProfileOwner = true)
+@EnsureHasProfileOwner
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface RequireRunOnWorkProfile {
+    OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+    /**
+     * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+     *
+     * <p>Only one device policy controller per test should be marked as primary.
+     */
+    boolean dpcIsPrimary() default false;
+
+    /**
+     * Affiliation ids to be set for the profile owner.
+     */
+    String[] affiliationIds() default {};
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default TRUE;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
new file mode 100644
index 0000000..75cbed0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
@@ -0,0 +1,52 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run on specified sdk versions.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device with the
+ * given version. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the sdk version is not correct.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireSdkVersion {
+    int min() default 1;
+    int max() default Integer.MAX_VALUE;
+    String reason() default "";
+    FailureMode failureMode() default FailureMode.SKIP;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java
new file mode 100644
index 0000000..81bd147
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireTargetSdkVersion.java
@@ -0,0 +1,51 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run in an apk targeting the specified sdk version.
+ *
+ * <p>Your test configuration may be configured so that this test is only run in an apk with the
+ * given version. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the target sdk version is not correct.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequireTargetSdkVersion {
+    int min() default 1;
+    int max() default Integer.MAX_VALUE;
+    FailureMode failureMode() default FailureMode.SKIP;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
new file mode 100644
index 0000000..e792d76
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
@@ -0,0 +1,52 @@
+/*
+ * 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.harrier.annotations;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test method should only run if a particular user type is supported
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a user which
+ * supports the user types. Otherwise, you can use {@code Devicestate} to ensure that the test is
+ * not run when the user type is not supported.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Repeatable(RequireUsersSupported.class)
+public @interface RequireUserSupported {
+    String value();
+    FailureMode failureMode() default FailureMode.SKIP;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default EARLY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequireUsersSupported.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java
new file mode 100644
index 0000000..e75903c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/RequiresNonGrantablePermission.java
@@ -0,0 +1,33 @@
+/*
+ * 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.harrier.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test is not in CTS only because it requires a permission that cannot be granted in
+ * CTS.
+ *
+ * <p>This has no impact on running tests, and is just for information for future maintainers.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface RequiresNonGrantablePermission {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/SlowApiTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java
new file mode 100644
index 0000000..cbf7bdb
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/StringTestParameter.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.annotations;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark a {@link String} parameter as being parameterised with the given values.
+ *
+ * <p>You must be using the {@link BedsteadJUnit4} test runner to use this annotation.
+ */
+@Target({ElementType.ANNOTATION_TYPE, ElementType.PARAMETER})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface StringTestParameter {
+    String[] value();
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTag.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/TestTags.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CanSetPolicyTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/CannotSetPolicyTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
new file mode 100644
index 0000000..89b4c8f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
@@ -0,0 +1,76 @@
+/*
+ * 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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that the given admin delegates the given scope to a test app.
+ *
+ * <p>You should use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. You can use {@code Devicestate#delegate()} to interact with
+ * the delegate.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasDelegate {
+
+    int ENSURE_HAS_DELEGATE_WEIGHT = DO_PO_WEIGHT + 1; // Should run after setting DO/PO
+
+    enum AdminType {
+        DEVICE_OWNER,
+        PROFILE_OWNER,
+        PRIMARY
+    }
+
+    /**
+     * The admin that should delegate this scope.
+     *
+     * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
+     * delegate will replace the primary dpc as primary without error.
+     */
+    AdminType admin();
+
+    /** The scope being delegated. */
+    String[] scopes();
+
+    /**
+     * Whether this delegate should be returned by calls to {@code Devicestate#policyManager()}.
+     *
+     * <p>Only one policy manager per test should be marked as primary.
+     */
+    boolean isPrimary() default false;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default ENSURE_HAS_DELEGATE_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
new file mode 100644
index 0000000..2626900c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
@@ -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.
+ */
+
+package com.android.bedstead.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.FailureMode;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that a device owner is available on the device.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * a device owner. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. If using {@code Devicestate}, you can use
+ * {@code Devicestate#deviceOwner()} to interact with the device owner.
+ *
+ * <p>When running on a device with a headless system user, enforcing this with {@code Devicestate}
+ * will also result in the profile owner of the current user being set to the same device policy
+ * controller.
+ *
+ * <p>If {@code Devicestate} is required to set the device owner (because there isn't one already)
+ * then all users and accounts may be removed from the device.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface EnsureHasDeviceOwner {
+
+    int DO_PO_WEIGHT = MIDDLE;
+
+     /** Behaviour if the device owner cannot be set. */
+    FailureMode failureMode() default FailureMode.FAIL;
+
+    /**
+     * Whether this DPC should be returned by calls to {@code Devicestate#dpc()} or
+     * {@code Devicestate#policyManager()}}.
+     *
+     * <p>Only one policy manager per test should be marked as primary.
+     */
+    boolean isPrimary() default false;
+
+    /**
+     * Affiliation ids to be set for the device owner.
+     */
+    String[] affiliationIds() default {};
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
new file mode 100644
index 0000000..224d89a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
@@ -0,0 +1,67 @@
+/*
+ * 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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.ENSURE_HAS_DELEGATE_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that the given admin does not delegate the given scope to a test app.
+ *
+ * <p>You should use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoDelegate {
+
+    enum AdminType {
+        DEVICE_OWNER,
+        PROFILE_OWNER,
+        PRIMARY,
+        ANY
+    }
+
+
+    /**
+     * The admin that should not delegate this scope.
+     *
+     * <p>Defaults to any admin
+     *
+     * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
+     * delegate will replace the primary dpc as primary without error.
+     */
+    AdminType admin() default AdminType.ANY;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default ENSURE_HAS_DELEGATE_WEIGHT + 1; // Should run after setting delegate
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
new file mode 100644
index 0000000..d7f1598
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
@@ -0,0 +1,50 @@
+/*
+ * 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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no device owner on the device.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * no device owner. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoDeviceOwner {
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
new file mode 100644
index 0000000..c222c75
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no dpc on the device.
+ *
+ * <p>This checks that there is no device owner, the current user has no work profiles, and the
+ * current user has no profile owner.
+ *
+ * <p>Your test configuration may be configured so that this test is only run on a device which has
+ * no dpc. Otherwise, you can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@EnsureHasNoDeviceOwner
+@EnsureHasNoWorkProfile
+@EnsureHasNoProfileOwner
+public @interface EnsureHasNoDpc {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
new file mode 100644
index 0000000..e4e964b
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
@@ -0,0 +1,53 @@
+/*
+ * 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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that there is no profile owner on the given user.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters the correct state for the
+ * method.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface EnsureHasNoProfileOwner {
+    /** Which user type the profile owner should not be attached to. */
+    UserType onUser() default CURRENT_USER;
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
new file mode 100644
index 0000000..4950290
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
@@ -0,0 +1,78 @@
+/*
+ * 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.harrier.annotations.enterprise;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
+import static com.android.bedstead.nene.packages.CommonPackages.FEATURE_DEVICE_ADMIN;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Mark that a test requires that a profile owner is set.
+ *
+ * <p>You can use {@code Devicestate} to ensure that the device enters
+ * the correct state for the method. If using {@code Devicestate}, you can use
+ * {@code Devicestate#profileOwner()} to interact with the profile owner.
+ */
+@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@RequireFeature(FEATURE_DEVICE_ADMIN)
+public @interface EnsureHasProfileOwner {
+    /** Which user type the work profile should be attached to. */
+    UserType onUser() default CURRENT_USER;
+
+    /**
+     * Whether this DPC should be returned by calls to {@code Devicestate#dpc()} or
+     * {@code Devicestate#policyManager()}}.
+     *
+     * <p>Only one policy manager per test should be marked as primary.
+     */
+    boolean isPrimary() default false;
+
+    /**
+     * If true, uses the {@code DevicePolicyManager#getParentProfileInstance(ComponentName)}
+     * instance of the dpc when calling to .dpc()
+     *
+     * <p>Only used if {@link #isPrimary()} is true.
+     */
+    boolean useParentInstance() default false;
+
+    /**
+     * Affiliation ids to be set for the profile owner.
+     */
+    String[] affiliationIds() default {};
+
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default DO_PO_WEIGHT;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnterprisePolicy.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/NegativePolicyTest.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/enterprise/PositivePolicyTest.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
new file mode 100644
index 0000000..75f610d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
@@ -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.
+ */
+
+package com.android.bedstead.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a given user does not have a specific profile type.
+ */
+@Target({})
+public @interface EnsureHasNoProfile {
+    UserType forUser() default CURRENT_USER;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
new file mode 100644
index 0000000..31740ca
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
@@ -0,0 +1,51 @@
+/*
+ * 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.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.UserType.CURRENT_USER;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+import com.android.bedstead.harrier.UserType;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a given user has a specific profile type.
+ */
+@Target({})
+public @interface EnsureHasProfile {
+    /** Which user type the profile should be attached to. */
+    UserType forUser() default CURRENT_USER;
+
+    /** Whether the test app should be installed in the profile. */
+    boolean installTestApp() default true;
+
+    /**
+     * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+     *
+     * <p>Only one device policy controller per test should be marked as primary.
+     */
+    // NOTE: This field is only required if hasProfileOwner=true
+    boolean dpcIsPrimary() default false;
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default ANY;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/ParameterizedAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RepeatingAnnotation.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
new file mode 100644
index 0000000..ce10411
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
@@ -0,0 +1,52 @@
+/*
+ * 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.harrier.annotations.meta;
+
+import static com.android.bedstead.harrier.OptionalBoolean.ANY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+
+import com.android.bedstead.harrier.OptionalBoolean;
+
+import java.lang.annotation.Target;
+
+/**
+ * This annotation should not be used directly. It exists as a template for annotations which
+ * ensure that a test runs on a given profile type.
+ */
+@Target({})
+public @interface RequireRunOnProfile {
+    OptionalBoolean installInstrumentedAppInParent() default ANY;
+
+    /**
+     * Whether the profile owner's DPC should be returned by calls to {@code Devicestate#dpc()}.
+     *
+     * <p>Only one device policy controller per test should be marked as primary.
+     */
+    // NOTE: This field is only required if hasProfileOwner=true
+    boolean dpcIsPrimary() default false;
+
+    /**
+     * Affiliation ids to be set for the profile owner.
+     */
+    // NOTE: This field is only required if hasProfileOwner=true
+    String[] affiliationIds() default {};
+
+    /**
+     * Should we ensure that we are switched to the parent of the profile.
+     */
+    OptionalBoolean switchedToParentUser() default TRUE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfileAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnUserAnnotation.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/meta/RequiresBedsteadJUnit4.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeNone.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedDeviceOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnAffiliatedProfileOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnBackgroundDeviceOwnerUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnDeviceOwnerUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfCorporateOwnedProfileOwner.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java
new file mode 100644
index 0000000..d70b32a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerUsingParentInstance.java
@@ -0,0 +1,54 @@
+/*
+ * 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.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on the parent of a profile owner and .dpc() relates to the
+ * parent instance of the dpc in the profile.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+@RequireRunOnPrimaryUser
+@EnsureHasNoDeviceOwner
+@EnsureHasWorkProfile(dpcIsPrimary = true, useParentInstanceOfDpc = true)
+public @interface IncludeRunOnParentOfProfileOwnerUsingParentInstance {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnPrimaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
new file mode 100644
index 0000000..96c73ab
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
@@ -0,0 +1,54 @@
+/*
+ * 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.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on the primary user
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+// Explicitly primary user which excludes headless users as this isn't a valid mode on headless
+@RequireRunOnPrimaryUser
+@EnsureHasNoDeviceOwner
+@EnsureHasProfileOwner(isPrimary = true)
+public @interface IncludeRunOnProfileOwnerPrimaryUser {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerProfileWithNoDeviceOwner.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
new file mode 100644
index 0000000..a243839
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
@@ -0,0 +1,53 @@
+/*
+ * 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.harrier.annotations.parameterized;
+
+import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
+
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Parameterize a test so that it runs on a device which has a primary and work profile, but runs
+ * the test on a secondary user which is in a different profile group.
+ */
+@Target({ElementType.METHOD, ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@ParameterizedAnnotation
+@RequireRunOnSecondaryUser
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER, dpcIsPrimary = true)
+public @interface IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile {
+    /**
+     * Weight sets the order that annotations will be resolved.
+     *
+     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
+     *
+     * <p>If there is an order requirement between annotations, ensure that the weight of the
+     * annotation which must be resolved first is lower than the one which must be resolved later.
+     *
+     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
+     */
+    int weight() default LATE;
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
new file mode 100644
index 0000000..833fdc9
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for account management tests.
+ *
+ * <p>This is used by {@code
+ * DevicePolicyManager#setAccountManagementDisabled(ComponentName, String, boolean)} and
+ * {@code DevicePolicyManager#getAccountTypesWithManagementDisabled()}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
+public final class AccountManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
new file mode 100644
index 0000000..19b7f78
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
@@ -0,0 +1,42 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_APP_RESTRICTIONS;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)} and
+ * {@code DevicePolicyManager#getApplicationRestrictions(ComponentName, String)}.
+ */
+@EnterprisePolicy(
+        dpc = {
+            APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED,
+            APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED},
+        delegatedScopes = DELEGATION_APP_RESTRICTIONS
+        )
+public final class ApplicationRestrictions {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
new file mode 100644
index 0000000..b41ac24
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
@@ -0,0 +1,37 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by the method
+ * {@code DevicePolicyManager#setApplicationRestrictionsManagingPackage(ComponentName, String, Bundle)}
+ */
+@EnterprisePolicy(
+        dpc = {
+                APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND,
+                APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class ApplicationRestrictionsManagingPackage {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
new file mode 100644
index 0000000..1cd71e2
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around installing/uninstalling CaCerts
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#installCaCert(ComponentName, byte[])} and
+ * {@code DevicePolicyManager#uninstallCaCert(ComponentName, byte[])}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class CaCertManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
new file mode 100644
index 0000000..5e2eeff
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for application restrictions.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setCameraDisabled(ComponentName, boolean)} and
+ * {@code DevicePolicyManager#getCameraDisabled(ComponentName)}.
+ */
+// TODO(b/201753989):  Update the profileOwner flag once the behaviour of setCameraDisabled
+//  is properly defined on secondary user POs.
+@EnterprisePolicy(dpc = {
+        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_GLOBALLY,
+        APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER
+})
+public class CameraPolicy {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
new file mode 100644
index 0000000..098a20d
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
@@ -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.
+ */
+
+package com.android.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for create and manage user.
+ *
+ * <p>This is used by methods such as {@code DevicePolicyManager#createAndManageUser(
+ * ComponentName, String, ComponentName, PersistableBundle, int)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
+public final class CreateAndManageUser {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
new file mode 100644
index 0000000..2f1494f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
@@ -0,0 +1,31 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for set default SMS application test.
+ *
+ * <p>This is used by {@code DevicePolicyManager#setDefaultSmsApplication(ComponentName, String)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
+public final class DefaultSmsApplication {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
new file mode 100644
index 0000000..4512f48
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for general admin delegation that doesn't have additional scope-specific constraints on
+ * the admin type. Specific delegations with these constraints have their own policies.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class Delegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java
new file mode 100644
index 0000000..a3fc08a
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DeprecatedResetPassword.java
@@ -0,0 +1,30 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for {@code DevicePolicyManager#resetPassword(String, int)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class DeprecatedResetPassword {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowNetworkReset.java
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
similarity index 100%
rename from common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
rename to common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/DisallowPrivateDnsConfig.java
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
new file mode 100644
index 0000000..c054f49
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for testing enrollment specific ID.
+ * See {@code DevicePolicyManager#getEnrollmentSpecificId()} for more detail.
+ */
+@EnterprisePolicy(dpc = {
+        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER,
+        APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER
+})
+public final class EnrollmentSpecificId {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
new file mode 100644
index 0000000..ef9fb68
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around key management
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#installKeyPair(ComponentName, PrivateKey, Certificate, String)} and
+ * {@code DevicePolicyManager#removeKeyPair(ComponentName, String)}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public final class KeyManagement {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java
new file mode 100644
index 0000000..823cf8e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockNow.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.nene.permissions.CommonPermissions.LOCK_DEVICE;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for {@code DevicePolicyManager#lockNow()}.
+ */
+// This is not applied by profile owner as the behaviour is different with a unified challenge
+@EnterprisePolicy(dpc =
+        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER,
+        permissions =
+        @EnterprisePolicy.Permission(appliedWith = LOCK_DEVICE, appliesTo = APPLIES_TO_OWN_USER))
+public final class LockNow {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
new file mode 100644
index 0000000..057824c
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
@@ -0,0 +1,37 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around Lock Task mode
+ * (https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode).
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} and
+ * {@code DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+                    APPLIED_BY_AFFILIATED_PROFILE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER})
+public final class LockTask {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
new file mode 100644
index 0000000..8b5b2bd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for network logging delegation.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@code
+ * DevicePolicyManager#DELEGATION_NETWORK_LOGGING}.
+ */
+@EnterprisePolicy(dpc = {
+        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER})
+public final class NetworkLoggingDelegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
new file mode 100644
index 0000000..82603be
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
@@ -0,0 +1,30 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for testing preferential network service.
+ * See {@code DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
+public final class PreferentialNetworkService {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
new file mode 100644
index 0000000..0d1a832
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around resetting a new device password
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#resetPasswordWithToken(ComponentName, String, byte[], int)}
+ */
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
+        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
+public class ResetPasswordWithToken {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
new file mode 100644
index 0000000..8cdb1b0
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for disabling screen capture.
+ *
+ * <p>Users of this policy are
+ * {@code DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
+        | APPLIED_BY_PARENT_INSTANCE_OF_COPE_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
+public final class ScreenCaptureDisabled {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
new file mode 100644
index 0000000..7dfcf6f
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
@@ -0,0 +1,34 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for security logging delegation.
+ *
+ * <p>This is used for methods such as {@code
+ * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@code
+ * DevicePolicyManager#DELEGATION_SECURITY_LOGGING}.
+ */
+// TODO(b/198774281): COPE profile POs can call this too, but we need to add the flag.
+@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
+public final class SecurityLoggingDelegation {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
new file mode 100644
index 0000000..7fe01ac
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.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.bedstead.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a basic permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting permissions not covered by other policies.
+ */
+@EnterprisePolicy(
+        dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED,
+        delegatedScopes = DELEGATION_PERMISSION_GRANT)
+public final class SetPermissionGrantState {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
new file mode 100644
index 0000000..b69851e
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
@@ -0,0 +1,33 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a sensor permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting sensor permissions.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
+public final class SetSensorPermissionGranted {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
new file mode 100644
index 0000000..b4d2a03
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
@@ -0,0 +1,40 @@
+/*
+ * 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.harrier.policies;
+
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
+import static com.android.bedstead.nene.devicepolicy.CommonDevicePolicy.DELEGATION_PERMISSION_GRANT;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policies around setting the grant state of a SMS related permission.
+ *
+ * <p>This is used by
+ * {@code DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
+ * granting sms-related permissions.
+ */
+// TODO(198311372): Check if APPLIED_BY_PROFILE_OWNER_USER is expected
+@EnterprisePolicy(
+        dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIED_BY_PROFILE_OWNER_USER | CAN_BE_DELEGATED,
+        delegatedScopes = DELEGATION_PERMISSION_GRANT)
+public final class SetSmsPermissionGranted {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
new file mode 100644
index 0000000..7f16f55
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
@@ -0,0 +1,35 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for long and short support messages.
+ *
+ * <p>Users of this policy are {@code DevicePolicyManager#setLongSupportMessage(ComponentName,
+ * CharSequence)}, {@code DevicePolicyManager#setShortSupportMessage(ComponentName, CharSequence)},
+ * {@code DevicePolicyManager#getLongSupportMessage(ComponentName)} and {@code
+ * DevicePolicyManager#getShortSupportMessage(ComponentName)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
+public final class SupportMessage {
+}
diff --git a/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
new file mode 100644
index 0000000..c47db87
--- /dev/null
+++ b/common/device-side/bedstead/harrier/common/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
@@ -0,0 +1,33 @@
+/*
+ * 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.harrier.policies;
+
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
+import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
+
+import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
+
+/**
+ * Policy for user control disabled packages.
+ *
+ * <p>This is used by methods such as
+ * {@code DevicePolicyManager#setUserControlDisabledPackages(ComponentName, List)} and
+ * {@code DevicePolicyManager#getUserControlDisabledPackages(ComponentName)}.
+ */
+@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_GLOBALLY)
+public final class UserControlDisabledPackages {
+}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
deleted file mode 100644
index 7ffabbe..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/BedsteadJUnit4.java
+++ /dev/null
@@ -1,489 +0,0 @@
-/*
- * 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.harrier;
-
-import android.os.Bundle;
-
-import androidx.annotation.Nullable;
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
-import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
-import com.android.bedstead.nene.exceptions.NeneException;
-
-import com.google.common.base.Objects;
-
-import org.junit.Test;
-import org.junit.rules.TestRule;
-import org.junit.runners.BlockJUnit4ClassRunner;
-import org.junit.runners.model.FrameworkMethod;
-import org.junit.runners.model.InitializationError;
-import org.junit.runners.model.TestClass;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-/**
- * A JUnit test runner for use with Bedstead.
- */
-public final class BedsteadJUnit4 extends BlockJUnit4ClassRunner {
-
-    private static final String BEDSTEAD_PACKAGE_NAME = "com.android.bedstead";
-
-    // These are annotations which are not included indirectly
-    private static final Set<String> sIgnoredAnnotationPackages = new HashSet<>();
-    static {
-        sIgnoredAnnotationPackages.add("java.lang.annotation");
-        sIgnoredAnnotationPackages.add("com.android.bedstead.harrier.annotations.meta");
-        sIgnoredAnnotationPackages.add("kotlin.*");
-        sIgnoredAnnotationPackages.add("org.junit");
-    }
-
-    private static int annotationSorter(Annotation a, Annotation b) {
-        return getAnnotationWeight(a) - getAnnotationWeight(b);
-    }
-
-    private static int getAnnotationWeight(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            // Special case, not important
-            return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
-        }
-
-        if (!annotation.annotationType().getPackage().getName().startsWith(BEDSTEAD_PACKAGE_NAME)) {
-            return AnnotationRunPrecedence.FIRST;
-        }
-
-        try {
-            return (int) annotation.annotationType().getMethod("weight").invoke(annotation);
-        } catch (NoSuchMethodException e) {
-            // Default to PRECEDENCE_NOT_IMPORTANT if no weight is found on the annotation.
-            return AnnotationRunPrecedence.PRECEDENCE_NOT_IMPORTANT;
-        } catch (IllegalAccessException | InvocationTargetException e) {
-            throw new NeneException("Failed to invoke weight on this annotation: " + annotation, e);
-        }
-    }
-
-    /**
-     * {@link FrameworkMethod} subclass which allows modifying the test name and annotations.
-     */
-    public static final class BedsteadFrameworkMethod extends FrameworkMethod {
-
-        private final Annotation mParameterizedAnnotation;
-        private final Map<Class<? extends Annotation>, Annotation> mAnnotationsMap =
-                new HashMap<>();
-        private Annotation[] mAnnotations;
-
-        public BedsteadFrameworkMethod(Method method) {
-            this(method, /* parameterizedAnnotation= */ null);
-        }
-
-        public BedsteadFrameworkMethod(Method method, Annotation parameterizedAnnotation) {
-            super(method);
-            mParameterizedAnnotation = parameterizedAnnotation;
-
-            calculateAnnotations();
-        }
-
-        private void calculateAnnotations() {
-            List<Annotation> annotations =
-                    new ArrayList<>(Arrays.asList(getDeclaringClass().getAnnotations()));
-            annotations.sort(BedsteadJUnit4::annotationSorter);
-
-            annotations.addAll(Arrays.stream(getMethod().getAnnotations())
-                    .sorted(BedsteadJUnit4::annotationSorter)
-                    .collect(Collectors.toList()));
-
-            parseEnterpriseAnnotations(annotations);
-
-            resolveRecursiveAnnotations(annotations, mParameterizedAnnotation);
-
-            this.mAnnotations = annotations.toArray(new Annotation[0]);
-            for (Annotation annotation : annotations) {
-                if (annotation instanceof DynamicParameterizedAnnotation) {
-                    continue; // don't return this
-                }
-                mAnnotationsMap.put(annotation.annotationType(), annotation);
-            }
-        }
-
-        @Override
-        public String getName() {
-            if (mParameterizedAnnotation == null) {
-                return super.getName();
-            }
-            return super.getName() + "[" + getParameterName(mParameterizedAnnotation) + "]";
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!super.equals(obj)) {
-                return false;
-            }
-
-            if (!(obj instanceof BedsteadFrameworkMethod)) {
-                return false;
-            }
-
-            BedsteadFrameworkMethod other = (BedsteadFrameworkMethod) obj;
-
-            return Objects.equal(mParameterizedAnnotation, other.mParameterizedAnnotation);
-        }
-
-        @Override
-        public Annotation[] getAnnotations() {
-            return mAnnotations;
-        }
-
-        @Override
-        public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
-            return (T) mAnnotationsMap.get(annotationType);
-        }
-    }
-
-    private static String getParameterName(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return ((DynamicParameterizedAnnotation) annotation).name();
-        }
-        return annotation.annotationType().getSimpleName();
-    }
-
-    /**
-     * Resolve annotations recursively.
-     *
-     * @param parameterizedAnnotation The class of the parameterized annotation to expand, if any
-     */
-    public static void resolveRecursiveAnnotations(List<Annotation> annotations,
-            @Nullable Annotation parameterizedAnnotation) {
-        int index = 0;
-        while (index < annotations.size()) {
-            Annotation annotation = annotations.get(index);
-            annotations.remove(index);
-            List<Annotation> replacementAnnotations =
-                    getReplacementAnnotations(annotation, parameterizedAnnotation);
-            replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-            annotations.addAll(index, replacementAnnotations);
-            index += replacementAnnotations.size();
-        }
-    }
-
-    private static boolean isParameterizedAnnotation(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return true;
-        }
-
-        return annotation.annotationType().getAnnotation(ParameterizedAnnotation.class) != null;
-    }
-
-    private static Annotation[] getIndirectAnnotations(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return ((DynamicParameterizedAnnotation) annotation).annotations();
-        }
-        return annotation.annotationType().getAnnotations();
-    }
-
-    private static boolean isRepeatingAnnotation(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return false;
-        }
-
-        return annotation.annotationType().getAnnotation(RepeatingAnnotation.class) != null;
-    }
-
-    private static List<Annotation> getReplacementAnnotations(Annotation annotation,
-            @Nullable Annotation parameterizedAnnotation) {
-        List<Annotation> replacementAnnotations = new ArrayList<>();
-
-        if (isRepeatingAnnotation(annotation)) {
-            try {
-                Annotation[] annotations =
-                        (Annotation[]) annotation.annotationType()
-                                .getMethod("value").invoke(annotation);
-                Collections.addAll(replacementAnnotations, annotations);
-                return replacementAnnotations;
-            } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
-                throw new NeneException("Error expanding repeated annotations", e);
-            }
-        }
-
-        if (isParameterizedAnnotation(annotation) && !annotation.equals(parameterizedAnnotation)) {
-            return replacementAnnotations;
-        }
-
-        for (Annotation indirectAnnotation : getIndirectAnnotations(annotation)) {
-            if (shouldSkipAnnotation(annotation)) {
-                continue;
-            }
-
-            replacementAnnotations.addAll(getReplacementAnnotations(
-                    indirectAnnotation, parameterizedAnnotation));
-        }
-
-        if (!(annotation instanceof DynamicParameterizedAnnotation)) {
-            // We drop the fake annotation once it's replaced
-            replacementAnnotations.add(annotation);
-        }
-
-        return replacementAnnotations;
-    }
-
-    private static boolean shouldSkipAnnotation(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return false;
-        }
-
-        String annotationPackage = annotation.annotationType().getPackage().getName();
-
-        for (String ignoredPackage : sIgnoredAnnotationPackages) {
-            if (ignoredPackage.endsWith(".*")) {
-                if (annotationPackage.startsWith(
-                    ignoredPackage.substring(0, ignoredPackage.length() - 2))) {
-                    return true;
-                }
-            } else if (annotationPackage.equals(ignoredPackage)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    public BedsteadJUnit4(Class<?> testClass) throws InitializationError {
-        super(testClass);
-    }
-
-    private boolean annotationShouldBeSkipped(Annotation annotation) {
-        if (annotation instanceof DynamicParameterizedAnnotation) {
-            return false;
-        }
-
-        return annotation.annotationType().equals(IncludeNone.class);
-    }
-
-    @Override
-    protected List<FrameworkMethod> computeTestMethods() {
-        TestClass testClass = getTestClass();
-
-        List<FrameworkMethod> basicTests = testClass.getAnnotatedMethods(Test.class);
-        List<FrameworkMethod> modifiedTests = new ArrayList<>();
-
-        for (FrameworkMethod m : basicTests) {
-            Set<Annotation> parameterizedAnnotations = getParameterizedAnnotations(m);
-
-            if (parameterizedAnnotations.isEmpty()) {
-                // Unparameterized, just add the original
-                modifiedTests.add(new BedsteadFrameworkMethod(m.getMethod()));
-            }
-
-            for (Annotation annotation : parameterizedAnnotations) {
-                if (annotationShouldBeSkipped(annotation)) {
-                    // Special case - does not generate a run
-                    continue;
-                }
-                modifiedTests.add(
-                        new BedsteadFrameworkMethod(m.getMethod(), annotation));
-            }
-        }
-
-        sortMethodsByBedsteadAnnotations(modifiedTests);
-
-        return modifiedTests;
-    }
-
-    /**
-     * Sort methods so that methods with identical bedstead annotations are together.
-     *
-     * <p>This will also ensure that all tests methods which are not annotated for bedstead will
-     * run before any tests which are annotated.
-     */
-    private void sortMethodsByBedsteadAnnotations(List<FrameworkMethod> modifiedTests) {
-        List<Annotation> bedsteadAnnotationsSortedByMostCommon =
-                bedsteadAnnotationsSortedByMostCommon(modifiedTests);
-
-        modifiedTests.sort((o1, o2) -> {
-            for (Annotation annotation : bedsteadAnnotationsSortedByMostCommon) {
-                boolean o1HasAnnotation = o1.getAnnotation(annotation.annotationType()) != null;
-                boolean o2HasAnnotation = o2.getAnnotation(annotation.annotationType()) != null;
-
-                if (o1HasAnnotation && !o2HasAnnotation) {
-                    // o1 goes to the end
-                    return 1;
-                } else if (o2HasAnnotation && !o1HasAnnotation) {
-                    return -1;
-                }
-            }
-            return 0;
-        });
-    }
-
-    private List<Annotation> bedsteadAnnotationsSortedByMostCommon(List<FrameworkMethod> methods) {
-        Map<Annotation, Integer> annotationCounts = countAnnotations(methods);
-        List<Annotation> annotations = new ArrayList<>(annotationCounts.keySet());
-
-        annotations.removeIf(
-                annotation ->
-                        !annotation.annotationType()
-                                .getCanonicalName().contains(BEDSTEAD_PACKAGE_NAME));
-
-        annotations.sort(Comparator.comparingInt(annotationCounts::get));
-        Collections.reverse(annotations);
-
-        return annotations;
-    }
-
-    private Map<Annotation, Integer> countAnnotations(List<FrameworkMethod> methods) {
-        Map<Annotation, Integer> annotationCounts = new HashMap<>();
-
-        for (FrameworkMethod method : methods) {
-            for (Annotation annotation : method.getAnnotations()) {
-                annotationCounts.put(
-                        annotation, annotationCounts.getOrDefault(annotation, 0) + 1);
-            }
-        }
-
-        return annotationCounts;
-    }
-
-    private Set<Annotation> getParameterizedAnnotations(FrameworkMethod method) {
-        Set<Annotation> parameterizedAnnotations = new HashSet<>();
-        List<Annotation> annotations = new ArrayList<>(Arrays.asList(method.getAnnotations()));
-
-        // TODO(scottjonathan): We're doing this twice... does it matter?
-        parseEnterpriseAnnotations(annotations);
-
-        for (Annotation annotation : annotations) {
-            if (isParameterizedAnnotation(annotation)) {
-                parameterizedAnnotations.add(annotation);
-            }
-        }
-
-        return parameterizedAnnotations;
-    }
-
-    /**
-     * Parse enterprise-specific annotations.
-     *
-     * <p>To be used before general annotation processing.
-     */
-    private static void parseEnterpriseAnnotations(List<Annotation> annotations) {
-        int index = 0;
-        while (index < annotations.size()) {
-            Annotation annotation = annotations.get(index);
-            if (annotation instanceof PositivePolicyTest) {
-                annotations.remove(index);
-                Class<?> policy = ((PositivePolicyTest) annotation).policy();
-
-                EnterprisePolicy enterprisePolicy =
-                        policy.getAnnotation(EnterprisePolicy.class);
-                List<Annotation> replacementAnnotations =
-                        Policy.positiveStates(policy.getName(), enterprisePolicy);
-                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
-                annotations.addAll(index, replacementAnnotations);
-                index += replacementAnnotations.size();
-            } else if (annotation instanceof NegativePolicyTest) {
-                annotations.remove(index);
-                Class<?> policy = ((NegativePolicyTest) annotation).policy();
-
-                EnterprisePolicy enterprisePolicy =
-                        policy.getAnnotation(EnterprisePolicy.class);
-                List<Annotation> replacementAnnotations =
-                        Policy.negativeStates(policy.getName(), enterprisePolicy);
-                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
-                annotations.addAll(index, replacementAnnotations);
-                index += replacementAnnotations.size();
-            } else if (annotation instanceof CannotSetPolicyTest) {
-                annotations.remove(index);
-                Class<?> policy = ((CannotSetPolicyTest) annotation).policy();
-
-                EnterprisePolicy enterprisePolicy =
-                        policy.getAnnotation(EnterprisePolicy.class);
-                List<Annotation> replacementAnnotations =
-                        Policy.cannotSetPolicyStates(policy.getName(), enterprisePolicy, ((CannotSetPolicyTest) annotation).includeDeviceAdminStates(), ((CannotSetPolicyTest) annotation).includeNonDeviceAdminStates());
-                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
-                annotations.addAll(index, replacementAnnotations);
-                index += replacementAnnotations.size();
-            } else if (annotation instanceof CanSetPolicyTest) {
-                annotations.remove(index);
-                Class<?> policy = ((CanSetPolicyTest) annotation).policy();
-                boolean singleTestOnly = ((CanSetPolicyTest) annotation).singleTestOnly();
-
-                EnterprisePolicy enterprisePolicy =
-                        policy.getAnnotation(EnterprisePolicy.class);
-                List<Annotation> replacementAnnotations =
-                        Policy.canSetPolicyStates(
-                                policy.getName(), enterprisePolicy, singleTestOnly);
-                replacementAnnotations.sort(BedsteadJUnit4::annotationSorter);
-
-                annotations.addAll(index, replacementAnnotations);
-                index += replacementAnnotations.size();
-            } else {
-                index++;
-            }
-        }
-    }
-
-    @Override
-    protected List<TestRule> classRules() {
-        List<TestRule> rules = super.classRules();
-
-        for (TestRule rule : rules) {
-            if (rule instanceof DeviceState) {
-                DeviceState deviceState = (DeviceState) rule;
-
-                deviceState.setSkipTestTeardown(true);
-                deviceState.setUsingBedsteadJUnit4(true);
-
-                break;
-            }
-        }
-
-        return rules;
-    }
-
-    /**
-     * True if the test is running in debug mode.
-     *
-     * <p>This will result in additional debugging information being added which would otherwise
-     * be dropped to improve test performance.
-     *
-     * <p>To enable this, pass the "bedstead-debug" instrumentation arg as "true"
-     */
-    public static boolean isDebug() {
-        Bundle arguments = InstrumentationRegistry.getArguments();
-        return Boolean.parseBoolean(arguments.getString("bedstead-debug", "false"));
-    }
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
index 75cf62b..9d1f576 100644
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
+++ b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/DeviceState.java
@@ -20,7 +20,7 @@
 import static android.app.ActivityManager.STOP_USER_ON_SWITCH_DEFAULT;
 import static android.app.ActivityManager.STOP_USER_ON_SWITCH_FALSE;
 
-import static com.android.bedstead.nene.permissions.Permissions.NOTIFY_PENDING_SYSTEM_UPDATE;
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
 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.utils.Versions.meetsSdkVersionRequirements;
@@ -39,6 +39,7 @@
 import android.os.Bundle;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.platform.app.InstrumentationRegistry;
 
@@ -47,10 +48,12 @@
 import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
 import com.android.bedstead.harrier.annotations.EnsurePackageNotInstalled;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsurePasswordSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
 import com.android.bedstead.harrier.annotations.FailureMode;
 import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
 import com.android.bedstead.harrier.annotations.RequireFeature;
-import com.android.bedstead.harrier.annotations.RequireGmsInstrumentation;
 import com.android.bedstead.harrier.annotations.RequireHeadlessSystemUserMode;
 import com.android.bedstead.harrier.annotations.RequireLowRamDevice;
 import com.android.bedstead.harrier.annotations.RequireNotHeadlessSystemUserMode;
@@ -58,6 +61,7 @@
 import com.android.bedstead.harrier.annotations.RequirePackageInstalled;
 import com.android.bedstead.harrier.annotations.RequirePackageNotInstalled;
 import com.android.bedstead.harrier.annotations.RequireSdkVersion;
+import com.android.bedstead.harrier.annotations.RequireTargetSdkVersion;
 import com.android.bedstead.harrier.annotations.RequireUserSupported;
 import com.android.bedstead.harrier.annotations.TestTag;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
@@ -89,6 +93,7 @@
 import com.android.bedstead.nene.utils.Versions;
 import com.android.bedstead.remotedpc.RemoteDelegate;
 import com.android.bedstead.remotedpc.RemoteDpc;
+import com.android.bedstead.remotedpc.RemoteDpcUsingParentInstance;
 import com.android.bedstead.remotedpc.RemotePolicyManager;
 import com.android.bedstead.testapp.TestApp;
 import com.android.bedstead.testapp.TestAppInstance;
@@ -100,7 +105,6 @@
 import junit.framework.AssertionFailedError;
 
 import org.junit.AssumptionViolatedException;
-import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.FrameworkMethod;
 import org.junit.runners.model.Statement;
@@ -134,9 +138,8 @@
  *
  * {@code assumeTrue} will be used, so tests which do not meet preconditions will be skipped.
  */
-public final class DeviceState implements TestRule {
+public final class DeviceState extends HarrierRule {
 
-    private static final String GMS_PKG = "com.google.android.gms";
     private static final ComponentName REMOTE_DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
 
     private static final String SWITCHED_TO_USER = "switchedToUser";
@@ -145,12 +148,15 @@
     public static final String FOR_USER = "forUser";
     public static final String DPC_IS_PRIMARY = "dpcIsPrimary";
     public static final String AFFILIATION_IDS = "affiliationIds";
+    private static final String USE_PARENT_INSTANCE_OF_DPC = "useParentInstanceOfDpc";
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
     private static final String SKIP_TEST_TEARDOWN_KEY = "skip-test-teardown";
     private static final String SKIP_CLASS_TEARDOWN_KEY = "skip-class-teardown";
     private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
     private static final String MIN_SDK_VERSION_KEY = "min-sdk-version";
+    private static final String PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY =
+            "permission-instrumentation-package";
     private boolean mSkipTestTeardown;
     private boolean mSkipClassTeardown;
     private boolean mSkipTests;
@@ -161,10 +167,13 @@
     // The minimum version supported by tests, defaults to current version
     private final int mMinSdkVersion;
     private int mMinSdkVersionCurrentTest;
+    private @Nullable String mPermissionsInstrumentationPackage;
+    private final Set<String> mPermissionsInstrumentationPackagePermissions = new HashSet<>();
 
-    // Marks if the conditions for requiring running under GMS instrumentation have been set
-    // if not - we assume the test should never run under GMS instrumentation
-    private boolean mHasRequireGmsInstrumentation = false;
+    // Marks if the conditions for requiring running under permission instrumentation have been set
+    // if not - we assume the test should never run under permission instrumentation
+    // This is only used if a permission instrumentation package is set
+    private boolean mHasRequirePermissionInstrumentation = false;
 
     private static final String TV_PROFILE_TYPE_NAME = "com.android.tv.profile";
 
@@ -177,12 +186,21 @@
         mSkipTestsReason = arguments.getString(SKIP_TESTS_REASON_KEY, "");
         mSkipTests = !mSkipTestsReason.isEmpty();
         mMinSdkVersion = arguments.getInt(MIN_SDK_VERSION_KEY, Build.VERSION.SDK_INT);
+        mPermissionsInstrumentationPackage =
+                arguments.getString(PERMISSIONS_INSTRUMENTATION_PACKAGE_KEY);
+        if (mPermissionsInstrumentationPackage != null) {
+            mPermissionsInstrumentationPackagePermissions.addAll(
+                    TestApis.packages().find(mPermissionsInstrumentationPackage)
+                            .requestedPermissions());
+        }
     }
 
+    @Override
     void setSkipTestTeardown(boolean skipTestTeardown) {
         mSkipTestTeardown = skipTestTeardown;
     }
 
+    @Override
     void setUsingBedsteadJUnit4(boolean usingBedsteadJUnit4) {
         mUsingBedsteadJUnit4 = usingBedsteadJUnit4;
     }
@@ -216,7 +234,7 @@
 
                     mMinSdkVersionCurrentTest = mMinSdkVersion;
                     List<Annotation> annotations = getAnnotations(description);
-                    permissionContext = applyAnnotations(annotations);
+                    permissionContext = applyAnnotations(annotations, /* isTest= */ true);
 
                     Log.d(LOG_TAG,
                             "Finished preparing state for test " + description.getMethodName());
@@ -241,7 +259,7 @@
             }};
     }
 
-    private PermissionContextImpl applyAnnotations(List<Annotation> annotations)
+    private PermissionContextImpl applyAnnotations(List<Annotation> annotations, boolean isTest)
             throws Throwable {
         PermissionContextImpl permissionContext = null;
         for (Annotation annotation : annotations) {
@@ -268,10 +286,18 @@
                                 .getMethod(INSTALL_INSTRUMENTED_APP).invoke(annotation);
 
                 boolean dpcIsPrimary = false;
+                boolean useParentInstance = false;
                 if (ensureHasProfileAnnotation.hasProfileOwner()) {
                     dpcIsPrimary = (boolean)
                             annotation.annotationType()
                                     .getMethod(DPC_IS_PRIMARY).invoke(annotation);
+
+                    if (dpcIsPrimary) {
+                        useParentInstance = (boolean)
+                                annotation.annotationType()
+                                        .getMethod(USE_PARENT_INSTANCE_OF_DPC).invoke(annotation);
+
+                    }
                 }
 
                 OptionalBoolean switchedToParentUser = (OptionalBoolean)
@@ -281,7 +307,7 @@
                 ensureHasProfile(
                         ensureHasProfileAnnotation.value(), installInstrumentedApp,
                         forUser, ensureHasProfileAnnotation.hasProfileOwner(),
-                        dpcIsPrimary, switchedToParentUser);
+                        dpcIsPrimary, useParentInstance, switchedToParentUser);
                 continue;
             }
 
@@ -348,6 +374,7 @@
                 requireRunOnProfile(requireRunOnProfileAnnotation.value(),
                         installInstrumentedAppInParent,
                         requireRunOnProfileAnnotation.hasProfileOwner(),
+                        /* useParentInstance= */ false,
                         dpcIsPrimary, switchedToParentUser, affiliationIds);
                 continue;
             }
@@ -407,6 +434,7 @@
                         (EnsureHasProfileOwner) annotation;
                 ensureHasProfileOwner(ensureHasProfileOwnerAnnotation.onUser(),
                         ensureHasProfileOwnerAnnotation.isPrimary(),
+                        ensureHasProfileOwnerAnnotation.useParentInstance(),
                         new HashSet<>(Arrays.asList(ensureHasProfileOwnerAnnotation.affiliationIds())));
                 continue;
             }
@@ -427,14 +455,6 @@
                 continue;
             }
 
-            if (annotation instanceof RequireGmsInstrumentation) {
-                RequireGmsInstrumentation requireGmsInstrumentationAnnotation =
-                        (RequireGmsInstrumentation) annotation;
-                requireGmsInstrumentation(requireGmsInstrumentationAnnotation.min(),
-                        requireGmsInstrumentationAnnotation.max());
-                continue;
-            }
-
             if (annotation instanceof RequireLowRamDevice) {
                 RequireLowRamDevice requireLowRamDeviceAnnotation =
                         (RequireLowRamDevice) annotation;
@@ -451,6 +471,18 @@
                 continue;
             }
 
+            if (annotation instanceof RequireTargetSdkVersion) {
+                RequireTargetSdkVersion requireTargetSdkVersionAnnotation =
+                        (RequireTargetSdkVersion) annotation;
+
+                requireTargetSdkVersion(
+                        requireTargetSdkVersionAnnotation.min(),
+                        requireTargetSdkVersionAnnotation.max(),
+                        requireTargetSdkVersionAnnotation.failureMode());
+
+                continue;
+            }
+
             if (annotation instanceof RequireSdkVersion) {
                 RequireSdkVersion requireSdkVersionAnnotation =
                         (RequireSdkVersion) annotation;
@@ -553,14 +585,35 @@
                 }
                 continue;
             }
+
+            if (annotation instanceof EnsureScreenIsOn) {
+                ensureScreenIsOn();
+                continue;
+            }
+
+            if (annotation instanceof EnsurePasswordSet) {
+                EnsurePasswordSet ensurePasswordSetAnnotation =
+                        (EnsurePasswordSet) annotation;
+                ensurePasswordSet(
+                        ensurePasswordSetAnnotation.forUser(),
+                        ensurePasswordSetAnnotation.password());
+                continue;
+            }
+
+            if (annotation instanceof EnsurePasswordNotSet) {
+                EnsurePasswordNotSet ensurePasswordNotSetAnnotation =
+                        (EnsurePasswordNotSet) annotation;
+                ensurePasswordNotSet(ensurePasswordNotSetAnnotation.forUser());
+                continue;
+            }
         }
 
         requireSdkVersion(/* min= */ mMinSdkVersionCurrentTest,
                 /* max= */ Integer.MAX_VALUE, FailureMode.SKIP);
 
-        if (!mHasRequireGmsInstrumentation) {
-            // TODO(scottjonathan): Only enforce if we've configured GMS Instrumentation
-            requireNoGmsInstrumentation();
+        if (isTest && mPermissionsInstrumentationPackage != null
+                && !mHasRequirePermissionInstrumentation) {
+            requireNoPermissionsInstrumentation();
         }
 
         return permissionContext;
@@ -636,7 +689,7 @@
                     try {
                         List<Annotation> annotations =
                                 new ArrayList<>(getAnnotations(description));
-                        permissionContext = applyAnnotations(annotations);
+                        permissionContext = applyAnnotations(annotations, /* isTest= */ false);
                     } catch (AssumptionViolatedException e) {
                         Log.i(LOG_TAG, "Assumption failed during class setup", e);
                         mSkipTests = true;
@@ -751,7 +804,7 @@
 
     private void requireRunOnProfile(String userType,
             OptionalBoolean installInstrumentedAppInParent,
-            boolean hasProfileOwner, boolean dpcIsPrimary,
+            boolean hasProfileOwner, boolean dpcIsPrimary, boolean useParentInstance,
             OptionalBoolean switchedToParentUser, Set<String> affiliationIds) {
         UserReference instrumentedUser = TestApis.users().instrumented();
 
@@ -773,7 +826,8 @@
         }
 
         if (hasProfileOwner) {
-            ensureHasProfileOwner(instrumentedUser, dpcIsPrimary, affiliationIds);
+            ensureHasProfileOwner(
+                    instrumentedUser, dpcIsPrimary, useParentInstance, affiliationIds);
         } else {
             ensureHasNoProfileOwner(instrumentedUser);
         }
@@ -799,38 +853,46 @@
                 !TestApis.packages().features().contains(feature), failureMode);
     }
 
-    private void requireNoGmsInstrumentation() {
-        boolean instrumentingGms =
-                TestApis.context().instrumentedContext().getPackageName().equals(GMS_PKG);
+    private void requireNoPermissionsInstrumentation() {
+        boolean instrumentingPermissions =
+                TestApis.context()
+                        .instrumentedContext().getPackageName()
+                        .equals(mPermissionsInstrumentationPackage);
 
         checkFailOrSkip(
-                "This test never runs using gms instrumentation",
-                !instrumentingGms,
+                "This test never runs using permissions instrumentation on this version"
+                        + " of Android",
+                !instrumentingPermissions,
                 FailureMode.SKIP
         );
     }
 
-    private void requireGmsInstrumentation(int min, int max) {
-        mHasRequireGmsInstrumentation = true;
-        boolean instrumentingGms =
-                TestApis.context().instrumentedContext().getPackageName().equals(GMS_PKG);
+    private void requirePermissionsInstrumentation() {
+        mHasRequirePermissionInstrumentation = true;
+        boolean instrumentingPermissions =
+                TestApis.context()
+                        .instrumentedContext().getPackageName()
+                        .equals(mPermissionsInstrumentationPackage);
 
-        if (meetsSdkVersionRequirements(min, max)) {
-            checkFailOrSkip(
-                    "For SDK versions between " + min +  " and " + max
-                            + " (inclusive), this test only runs when using gms instrumentation",
-                    instrumentingGms,
-                    FailureMode.SKIP
-            );
-        } else {
-            checkFailOrSkip(
-                    "For SDK versions between " + min +  " and " + max
-                            + " (inclusive), this test only runs when not using gms "
-                            + "instrumentation",
-                    !instrumentingGms,
-                    FailureMode.SKIP
-            );
-        }
+        checkFailOrSkip(
+                "This test only runs when using permissions instrumentation on this"
+                        + " version of Android",
+                instrumentingPermissions,
+                FailureMode.SKIP
+        );
+    }
+
+    private void requireTargetSdkVersion(
+            int min, int max, FailureMode failureMode) {
+
+        int targetSdkVersion = TestApis.packages().instrumented().targetSdkVersion();
+
+        checkFailOrSkip(
+                "TargetSdkVersion must be between " + min + " and " + max
+                        + " (inclusive) (version is " + targetSdkVersion + ")",
+                min <= targetSdkVersion && max >= targetSdkVersion,
+                failureMode
+        );
     }
 
     private void requireSdkVersion(int min, int max, FailureMode failureMode) {
@@ -881,17 +943,6 @@
         }
     }
 
-    public enum UserType {
-        /** Only to be used with annotations. */
-        ANY,
-        SYSTEM_USER,
-        CURRENT_USER,
-        PRIMARY_USER,
-        SECONDARY_USER,
-        WORK_PROFILE,
-        TV_PROFILE,
-    }
-
     private static final String LOG_TAG = "DeviceState";
 
     private static final Context sContext = TestApis.context().instrumentedContext();
@@ -906,6 +957,7 @@
 
     private final List<UserReference> mCreatedUsers = new ArrayList<>();
     private final List<UserBuilder> mRemovedUsers = new ArrayList<>();
+    private final List<UserReference> mUsersSetPasswords = new ArrayList<>();
     private final List<BlockingBroadcastReceiver> mRegisteredBroadcastReceivers = new ArrayList<>();
     private boolean mHasChangedDeviceOwner = false;
     private DevicePolicyController mOriginalDeviceOwner;
@@ -1134,6 +1186,7 @@
             UserType forUser,
             boolean hasProfileOwner,
             boolean profileOwnerIsPrimary,
+            boolean useParentInstance,
             OptionalBoolean switchedToParentUser) {
         com.android.bedstead.nene.users.UserType resolvedUserType =
                 requireUserSupported(profileType, FailureMode.SKIP);
@@ -1166,7 +1219,8 @@
         mProfiles.get(resolvedUserType).put(forUserReference, profile);
 
         if (hasProfileOwner) {
-            ensureHasProfileOwner(profile, profileOwnerIsPrimary, /* affiliationIds= */ null);
+            ensureHasProfileOwner(
+                    profile, profileOwnerIsPrimary, useParentInstance, /* affiliationIds= */ null);
         }
 
         ensureSwitchedToUser(switchedToParentUser, forUserReference);
@@ -1340,7 +1394,9 @@
                     mDeviceOwner.remove();
                 }
             } else if (!mOriginalDeviceOwner.equals(mDeviceOwner)) {
-                mDeviceOwner.remove();
+                if (mDeviceOwner != null) {
+                    mDeviceOwner.remove();
+                }
                 TestApis.devicePolicy().setDeviceOwner(
                         mOriginalDeviceOwner.componentName());
             }
@@ -1369,6 +1425,15 @@
         }
         mChangedProfileOwners.clear();
 
+        for (UserReference user : mUsersSetPasswords) {
+            if (mCreatedUsers.contains(user)) {
+                continue; // Will be removed anyway
+            }
+            user.clearPassword();
+        }
+
+        mUsersSetPasswords.clear();
+
         for (UserReference user : mCreatedUsers) {
             user.remove();
         }
@@ -1586,14 +1651,16 @@
                 .setAffiliationIds(REMOTE_DPC_COMPONENT_NAME, affiliationIds);
     }
 
-    private void ensureHasProfileOwner(UserType onUser, boolean isPrimary, Set<String> affiliationIds) {
+    private void ensureHasProfileOwner(UserType onUser, boolean isPrimary,
+            boolean useParentInstance, Set<String> affiliationIds) {
         // TODO(scottjonathan): Should support non-remotedpc profile owner (default to remotedpc)
         UserReference user = resolveUserTypeToUser(onUser);
-        ensureHasProfileOwner(user, isPrimary, affiliationIds);
+        ensureHasProfileOwner(user, isPrimary, useParentInstance, affiliationIds);
     }
 
     private void ensureHasProfileOwner(
-            UserReference user, boolean isPrimary, Set<String> affiliationIds) {
+            UserReference user, boolean isPrimary, boolean useParentInstance,
+            Set<String> affiliationIds) {
         if (isPrimary && mPrimaryPolicyManager != null
                 && !user.equals(mPrimaryPolicyManager.user())) {
             throw new IllegalStateException("Only one DPC can be marked as primary per test");
@@ -1625,7 +1692,14 @@
         }
 
         if (isPrimary) {
-            mPrimaryPolicyManager = RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
+            if (useParentInstance) {
+                mPrimaryPolicyManager = new RemoteDpcUsingParentInstance(
+                        RemoteDpc.forDevicePolicyController(
+                                mProfileOwners.get(user)).devicePolicyController());
+            } else {
+                mPrimaryPolicyManager =
+                        RemoteDpc.forDevicePolicyController(mProfileOwners.get(user));
+            }
         }
 
         if (affiliationIds != null) {
@@ -1833,16 +1907,39 @@
     }
 
     private void ensureCanGetPermission(String permission) {
-        // TODO(scottjonathan): Apply gms permission switches automatically rather than hard-coding
-        // TODO(scottjonathan): Add a config to only enforce gms permission when needed
-        if (permission.equals(NOTIFY_PENDING_SYSTEM_UPDATE)) {
-            requireGmsInstrumentation(1, Build.VERSION_CODES.R);
+        if (mPermissionsInstrumentationPackage == null) {
+            // We just need to check if we can get it generally
+
+            if (TestApis.permissions().usablePermissions().contains(permission)) {
+                return;
+            }
+
+            if (TestApis.packages().instrumented().isInstantApp()) {
+                // Instant Apps aren't able to know the permissions of shell so we can't know if we
+                // can adopt it - we'll assume we can adopt and log
+                Log.i(LOG_TAG,
+                        "Assuming we can get permission " + permission
+                                + " as running on instant app");
+                return;
+            }
+
+            TestApis.permissions().throwPermissionException(
+                    "Can not get required permission", permission);
         }
-        // TODO(scottjonathan): Apply version-specific constraints automatically
-        if (permission.equals(INTERACT_ACROSS_USERS_FULL)) {
-            requireSdkVersion(
-                    Build.VERSION_CODES.Q, Integer.MAX_VALUE, FailureMode.SKIP,
-                    "This test requires INTERACT_ACROSS_USERS_FULL which can only be used on Q+");
+
+        if (TestApis.permissions().adoptablePermissions().contains(permission)) {
+            requireNoPermissionsInstrumentation();
+        } else if (mPermissionsInstrumentationPackagePermissions.contains(permission)) {
+            requirePermissionsInstrumentation();
+        } else {
+            // Can't get permission at all - error (including the permissions for both)
+            TestApis.permissions().throwPermissionException(
+                    "Can not get permission including by instrumenting "
+                            + mPermissionsInstrumentationPackage
+                            + "\n " + mPermissionsInstrumentationPackage + " permissions: "
+                            + mPermissionsInstrumentationPackagePermissions,
+                    permission
+            );
         }
     }
 
@@ -1911,4 +2008,41 @@
                         .isLowRamDevice(),
                 failureMode);
     }
+
+    private void ensureScreenIsOn() {
+        TestApis.device().wakeUp();
+    }
+
+    private void ensurePasswordSet(UserType forUser, String password) {
+        UserReference user = resolveUserTypeToUser(forUser);
+
+        if (user.hasPassword()) {
+            return;
+        }
+
+        try {
+            user.setPassword(password);
+        } catch (NeneException e) {
+            throw new AssertionError("Require password set but error when setting password", e);
+        }
+        mUsersSetPasswords.add(user);
+    }
+
+    private void ensurePasswordNotSet(UserType forUser) {
+        UserReference user = resolveUserTypeToUser(forUser);
+
+        if (!user.hasPassword()) {
+            return;
+        }
+
+        try {
+            user.clearPassword(DEFAULT_PASSWORD);
+        } catch (NeneException e
+        ) {
+            throw new AssertionError(
+                    "Test requires user " + user + " does not have a password. "
+                            + "Password is set and is not DEFAULT_PASSWORD.");
+        }
+        mUsersSetPasswords.remove(user);
+    }
 }
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java
deleted file mode 100644
index e01c8f1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/Policy.java
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * 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.harrier;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_BLOCK_UNINSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_INSTALL;
-import static android.app.admin.DevicePolicyManager.DELEGATION_CERT_SELECTION;
-import static android.app.admin.DevicePolicyManager.DELEGATION_ENABLE_SYSTEM_APP;
-import static android.app.admin.DevicePolicyManager.DELEGATION_INSTALL_EXISTING_PACKAGE;
-import static android.app.admin.DevicePolicyManager.DELEGATION_KEEP_UNINSTALLED_PACKAGES;
-import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PACKAGE_ACCESS;
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_AFFILIATED_OTHER_USERS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_PARENT;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_UNAFFILIATED_OTHER_USERS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.DO_NOT_APPLY_TO_NEGATIVE_TESTS;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.NO;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate;
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeNone;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedDeviceOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnAffiliatedProfileOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfCorporateOwnedProfileOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
-import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser;
-
-import com.google.auto.value.AutoAnnotation;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-
-import java.lang.annotation.Annotation;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-/**
- * Utility class for enterprise policy tests.
- */
-public final class Policy {
-
-    // Delegate scopes to be used for a "CannotSet" state. All delegate scopes except the ones which
-    // should allow use of the API will be granted
-    private static final ImmutableSet<String> ALL_DELEGATE_SCOPES = ImmutableSet.of(
-            DELEGATION_CERT_INSTALL,
-            DELEGATION_APP_RESTRICTIONS,
-            DELEGATION_BLOCK_UNINSTALL,
-            DELEGATION_PERMISSION_GRANT,
-            DELEGATION_PACKAGE_ACCESS,
-            DELEGATION_ENABLE_SYSTEM_APP,
-            DELEGATION_INSTALL_EXISTING_PACKAGE,
-            DELEGATION_KEEP_UNINSTALLED_PACKAGES,
-            DELEGATION_NETWORK_LOGGING,
-            DELEGATION_CERT_SELECTION,
-            DELEGATION_SECURITY_LOGGING
-    );
-
-    // This is a map containing all Include* annotations and the flags which lead to them
-    // This is not validated - every state must have a single APPLIED_BY annotation
-    private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
-            STATE_ANNOTATIONS =
-            ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnDeviceOwnerUser()))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnDeviceOwnerUser(), /* isPrimary= */ true))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND, singleAnnotation(includeRunOnBackgroundDeviceOwnerUser()))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnBackgroundDeviceOwnerUser(), /* isPrimary= */ true))
-
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser()))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnNonAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser()))
-                    .put(APPLIED_BY_DEVICE_OWNER | APPLIES_TO_AFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedDeviceOwnerSecondaryUser(), /* isPrimary= */ true))
-
-                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
-                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnUnaffiliatedProfileOwnerSecondaryUser(), /* isPrimary= */ true))
-                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
-                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerPrimaryUser(), /* isPrimary= */ true))
-
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner(), /* isPrimary= */ true))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT, singleAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_PARENT | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnParentOfProfileOwnerWithNoDeviceOwner(), /* isPrimary= */ true))
-
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS, singleAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE | APPLIES_TO_UNAFFILIATED_OTHER_USERS | CAN_BE_DELEGATED, generateDelegateAnnotation(includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile(), /* isPrimary= */ true))
-                    .build();
-    // This must contain one key for every APPLIED_BY that is being used, and maps to the
-    // "default" for testing that DPC type
-    // in general this will be a state which runs on the same user as the dpc.
-    private static final ImmutableMap<Integer, Function<EnterprisePolicy, Set<Annotation>>>
-            DPC_STATE_ANNOTATIONS_BASE =
-            ImmutableMap.<Integer, Function<EnterprisePolicy, Set<Annotation>>>builder()
-                    .put(APPLIED_BY_DEVICE_OWNER, (flags) -> hasFlag(flags.dpc(), APPLIED_BY_DEVICE_OWNER | APPLIES_IN_BACKGROUND) ? ImmutableSet.of(includeRunOnBackgroundDeviceOwnerUser()) : ImmutableSet.of(includeRunOnDeviceOwnerUser()))
-                    .put(APPLIED_BY_AFFILIATED_PROFILE_OWNER, singleAnnotation(includeRunOnAffiliatedProfileOwnerSecondaryUser()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
-                    .put(APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO, singleAnnotation(includeRunOnProfileOwnerPrimaryUser()))
-                    .put(APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE, singleAnnotation(includeRunOnProfileOwnerProfileWithNoDeviceOwner()))
-                    .build();
-    private static final Map<Integer, Function<EnterprisePolicy, Set<Annotation>>>
-            DPC_STATE_ANNOTATIONS = DPC_STATE_ANNOTATIONS_BASE.entrySet().stream()
-            .collect(Collectors.toMap(Map.Entry::getKey, Policy::addGeneratedStates));
-    private static final int APPLIED_BY_FLAGS =
-            APPLIED_BY_DEVICE_OWNER | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_PROFILE
-                    | APPLIED_BY_AFFILIATED_PROFILE_OWNER_PROFILE
-                    | APPLIED_BY_UNAFFILIATED_PROFILE_OWNER_USER
-                    | APPLIED_BY_AFFILIATED_PROFILE_OWNER_USER;
-    private static final Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>>
-            ANNOTATIONS_MAP = calculateAnnotationsMap(STATE_ANNOTATIONS);
-
-    private Policy() {
-
-    }
-
-    @AutoAnnotation
-    private static IncludeNone includeNone() {
-        return new AutoAnnotation_Policy_includeNone();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnDeviceOwnerUser includeRunOnDeviceOwnerUser() {
-        return new AutoAnnotation_Policy_includeRunOnDeviceOwnerUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser includeRunOnNonAffiliatedDeviceOwnerSecondaryUser() {
-        return new AutoAnnotation_Policy_includeRunOnNonAffiliatedDeviceOwnerSecondaryUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnAffiliatedDeviceOwnerSecondaryUser includeRunOnAffiliatedDeviceOwnerSecondaryUser() {
-        return new AutoAnnotation_Policy_includeRunOnAffiliatedDeviceOwnerSecondaryUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnAffiliatedProfileOwnerSecondaryUser includeRunOnAffiliatedProfileOwnerSecondaryUser() {
-        return new AutoAnnotation_Policy_includeRunOnAffiliatedProfileOwnerSecondaryUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnUnaffiliatedProfileOwnerSecondaryUser includeRunOnUnaffiliatedProfileOwnerSecondaryUser() {
-        return new AutoAnnotation_Policy_includeRunOnUnaffiliatedProfileOwnerSecondaryUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnProfileOwnerProfileWithNoDeviceOwner includeRunOnProfileOwnerProfileWithNoDeviceOwner() {
-        return new AutoAnnotation_Policy_includeRunOnProfileOwnerProfileWithNoDeviceOwner();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile() {
-        return new AutoAnnotation_Policy_includeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner includeRunOnParentOfProfileOwnerWithNoDeviceOwner() {
-        return new AutoAnnotation_Policy_includeRunOnParentOfProfileOwnerWithNoDeviceOwner();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnParentOfCorporateOwnedProfileOwner includeRunOnParentOfCorporateOwnedProfileOwner() {
-        return new AutoAnnotation_Policy_includeRunOnParentOfCorporateOwnedProfileOwner();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnProfileOwnerPrimaryUser includeRunOnProfileOwnerPrimaryUser() {
-        return new AutoAnnotation_Policy_includeRunOnProfileOwnerPrimaryUser();
-    }
-
-    @AutoAnnotation
-    private static IncludeRunOnBackgroundDeviceOwnerUser includeRunOnBackgroundDeviceOwnerUser() {
-        return new AutoAnnotation_Policy_includeRunOnBackgroundDeviceOwnerUser();
-    }
-
-    @AutoAnnotation
-    private static EnsureHasDelegate ensureHasDelegate(EnsureHasDelegate.AdminType admin,
-            String[] scopes, boolean isPrimary) {
-        return new AutoAnnotation_Policy_ensureHasDelegate(admin, scopes, isPrimary);
-    }
-
-    private static Function<EnterprisePolicy, Set<Annotation>> singleAnnotation(
-            Annotation annotation) {
-        return (i) -> ImmutableSet.of(annotation);
-    }
-
-    private static Function<EnterprisePolicy, Set<Annotation>> generateDelegateAnnotation(
-            Annotation annotation, boolean isPrimary) {
-        return (policy) -> {
-            Annotation[] existingAnnotations = annotation.annotationType().getAnnotations();
-            return Arrays.stream(policy.delegatedScopes())
-                    .map(scope -> {
-                        Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
-                                existingAnnotations.length + 1);
-                        newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
-                                EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope},
-                                isPrimary);
-
-                        return new DynamicParameterizedAnnotation(
-                                annotation.annotationType().getSimpleName() + "Delegate:" + scope,
-                                newAnnotations);
-                    }).collect(Collectors.toSet());
-        };
-    }
-
-    private static Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> calculateAnnotationsMap(
-            Map<Integer, Function<EnterprisePolicy, Set<Annotation>>> annotations) {
-        Map<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> b = new HashMap<>();
-
-        for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> i :
-                annotations.entrySet()) {
-            if (!b.containsKey(i.getValue())) {
-                b.put(i.getValue(), new HashSet<>());
-            }
-
-            b.get(i.getValue()).add(i.getKey());
-        }
-
-        return b;
-    }
-
-    private static Function<EnterprisePolicy, Set<Annotation>> addGeneratedStates(
-            ImmutableMap.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> entry) {
-        return (policy) -> {
-            if (hasFlag(policy.dpc(), entry.getKey() | CAN_BE_DELEGATED)) {
-                Set<Annotation> results = new HashSet<>(entry.getValue().apply(policy));
-                results.addAll(results.stream().flatMap(
-                        t -> generateDelegateAnnotation(t, /* isPrimary= */ true).apply(
-                                policy).stream())
-                        .collect(Collectors.toSet()));
-
-                return results;
-            }
-
-            return entry.getValue().apply(policy);
-        };
-    }
-
-
-    /**
-     * Get parameterized test runs for the given policy.
-     *
-     * <p>These are states which should be run where the policy is able to be applied.
-     */
-    public static List<Annotation> positiveStates(String policyName,
-            EnterprisePolicy enterprisePolicy) {
-        Set<Annotation> annotations = new HashSet<>();
-
-        validateFlags(policyName, enterprisePolicy.dpc());
-
-        for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
-                ANNOTATIONS_MAP.entrySet()) {
-            if (isPositive(enterprisePolicy.dpc(), annotation.getValue())) {
-                annotations.addAll(annotation.getKey().apply(enterprisePolicy));
-            }
-        }
-
-        if (annotations.isEmpty()) {
-            // Don't run the original test unparameterized
-            annotations.add(includeNone());
-        }
-
-        return new ArrayList<>(annotations);
-    }
-
-    private static boolean isPositive(int[] policyFlags, Set<Integer> annotationFlags) {
-        for (int annotationFlag : annotationFlags) {
-            if (hasFlag(policyFlags, annotationFlag)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean isNegative(int[] policyFlags, Set<Integer> annotationFlags) {
-        for (int annotationFlag : annotationFlags) {
-            if (hasFlag(annotationFlag, DO_NOT_APPLY_TO_NEGATIVE_TESTS, /* nonMatchingFlag= */
-                    NO)) {
-                return false; // We don't support using this annotation for negative tests
-            }
-
-            int appliedByFlag = APPLIED_BY_FLAGS & annotationFlag;
-            int otherFlags = annotationFlag ^ appliedByFlag; // remove the appliedByFlag
-            if (hasFlag(policyFlags, /* matchingFlag= */ appliedByFlag, /* nonMatchingFlag= */
-                    otherFlags)) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    /**
-     * Get negative parameterized test runs for the given policy.
-     *
-     * <p>These are states which should be run where the policy is not able to be applied.
-     */
-    public static List<Annotation> negativeStates(String policyName,
-            EnterprisePolicy enterprisePolicy) {
-        Set<Annotation> annotations = new HashSet<>();
-
-        validateFlags(policyName, enterprisePolicy.dpc());
-
-        for (Map.Entry<Function<EnterprisePolicy, Set<Annotation>>, Set<Integer>> annotation :
-                ANNOTATIONS_MAP.entrySet()) {
-            if (isNegative(enterprisePolicy.dpc(), annotation.getValue())) {
-                annotations.addAll(annotation.getKey().apply(enterprisePolicy));
-            }
-        }
-
-        if (annotations.isEmpty()) {
-            // Don't run the original test unparameterized
-            annotations.add(includeNone());
-        }
-
-        return new ArrayList<>(annotations);
-    }
-
-    /**
-     * Get parameterized test runs where the policy cannot be set for the given policy.
-     */
-    public static List<Annotation> cannotSetPolicyStates(String policyName,
-            EnterprisePolicy enterprisePolicy, boolean includeDeviceAdminStates,
-            boolean includeNonDeviceAdminStates) {
-        Set<Annotation> annotations = new HashSet<>();
-
-        validateFlags(policyName, enterprisePolicy.dpc());
-
-        if (includeDeviceAdminStates) {
-            int allFlags = 0;
-            for (int p : enterprisePolicy.dpc()) {
-                allFlags = allFlags | p;
-            }
-
-            for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
-                    DPC_STATE_ANNOTATIONS.entrySet()) {
-                if ((appliedByFlag.getKey() & allFlags) == 0) {
-                    annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
-                }
-            }
-        }
-
-        if (includeNonDeviceAdminStates) {
-            Set<String> validScopes = ImmutableSet.copyOf(enterprisePolicy.delegatedScopes());
-            String[] scopes = ALL_DELEGATE_SCOPES.stream()
-                    .filter(i -> !validScopes.contains(i))
-                    .toArray(String[]::new);
-            Annotation[] existingAnnotations = IncludeRunOnDeviceOwnerUser.class.getAnnotations();
-
-            if (BedsteadJUnit4.isDebug()) {
-                // Add a non-DPC with no delegate scopes
-                Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
-                        existingAnnotations.length + 1);
-                newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
-                        EnsureHasDelegate.AdminType.PRIMARY, new String[]{}, /* isPrimary= */ true);
-                annotations.add(
-                        new DynamicParameterizedAnnotation("DelegateWithNoScopes", newAnnotations));
-
-                for (String scope : scopes) {
-                    newAnnotations = Arrays.copyOf(existingAnnotations,
-                            existingAnnotations.length + 1);
-                    newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
-                            EnsureHasDelegate.AdminType.PRIMARY, new String[]{scope}, /* isPrimary= */ true);
-                    annotations.add(
-                            new DynamicParameterizedAnnotation("DelegateWithScope:" + scope, newAnnotations));
-                }
-            } else {
-                Annotation[] newAnnotations = Arrays.copyOf(existingAnnotations,
-                        existingAnnotations.length + 1);
-                newAnnotations[newAnnotations.length - 1] = ensureHasDelegate(
-                        EnsureHasDelegate.AdminType.PRIMARY, scopes, /* isPrimary= */ true);
-                annotations.add(
-                        new DynamicParameterizedAnnotation("DelegateWithoutValidScope", newAnnotations));
-            }
-        }
-
-        if (annotations.isEmpty()) {
-            // Don't run the original test unparameterized
-            annotations.add(includeNone());
-        }
-
-        return new ArrayList<>(annotations);
-    }
-
-    /**
-     * Get state annotations where the policy can be set for the given policy.
-     */
-    public static List<Annotation> canSetPolicyStates(
-            String policyName, EnterprisePolicy enterprisePolicy, boolean singleTestOnly) {
-        Set<Annotation> annotations = new HashSet<>();
-
-        validateFlags(policyName, enterprisePolicy.dpc());
-
-        int allFlags = 0;
-        for (int p : enterprisePolicy.dpc()) {
-            allFlags = allFlags | p;
-        }
-
-        for (Map.Entry<Integer, Function<EnterprisePolicy, Set<Annotation>>> appliedByFlag :
-                DPC_STATE_ANNOTATIONS.entrySet()) {
-            if ((appliedByFlag.getKey() & allFlags) == appliedByFlag.getKey()) {
-                annotations.addAll(appliedByFlag.getValue().apply(enterprisePolicy));
-            }
-        }
-
-        if (annotations.isEmpty()) {
-            // Don't run the original test unparameterized
-            annotations.add(includeNone());
-        }
-
-        List<Annotation> annotationList = new ArrayList<>(annotations);
-
-        if (singleTestOnly) {
-            // We select one annotation in an arbitrary but deterministic way
-            annotationList.sort(Comparator.comparing(
-                    a -> a instanceof DynamicParameterizedAnnotation
-                            ? "DynamicParameterizedAnnotation" : a.annotationType().getName()));
-
-            // We don't want a delegate to be the representative test
-            Annotation firstAnnotation = annotationList.stream()
-                    .filter(i -> !(i instanceof  DynamicParameterizedAnnotation))
-                    .findFirst().get();
-            annotationList.clear();
-            annotationList.add(firstAnnotation);
-        }
-
-        return annotationList;
-    }
-
-    private static void validateFlags(String policyName, int[] values) {
-        int usedAppliedByFlags = 0;
-
-        for (int value : values) {
-            validateFlags(policyName, value);
-            int newUsedAppliedByFlags = usedAppliedByFlags | (value & APPLIED_BY_FLAGS);
-            if (newUsedAppliedByFlags == usedAppliedByFlags) {
-                throw new IllegalStateException(
-                        "Cannot have more than one policy flag APPLIED by the same component. "
-                                + "Error in policy " + policyName);
-            }
-            usedAppliedByFlags = newUsedAppliedByFlags;
-        }
-    }
-
-    private static void validateFlags(String policyName, int value) {
-        int matchingAppliedByFlags = APPLIED_BY_FLAGS & value;
-
-        if (matchingAppliedByFlags == 0) {
-            throw new IllegalStateException(
-                    "All policy flags must specify 1 APPLIED_BY flag. Policy " + policyName
-                            + " did not.");
-        }
-    }
-
-    private static boolean hasFlag(int[] values, int matchingFlag) {
-        return hasFlag(values, matchingFlag, /* nonMatchingFlag= */ NO);
-    }
-
-    private static boolean hasFlag(int[] values, int matchingFlag, int nonMatchingFlag) {
-        for (int value : values) {
-            if (hasFlag(value, matchingFlag, nonMatchingFlag)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private static boolean hasFlag(int value, int matchingFlag, int nonMatchingFlag) {
-        if (!((value & matchingFlag) == matchingFlag)) {
-            return false;
-        }
-
-        if (nonMatchingFlag != NO) {
-            return (value & nonMatchingFlag) != nonMatchingFlag;
-        }
-
-        return true;
-    }
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
deleted file mode 100644
index 1241503..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/AfterClass.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Replacement for {@link org.junit.AfterClass} for use by classes which use {@link DeviceState}.
- *
- * <p>Methods annotated {@link AfterClass} must be public, static, must return {@code void}, and
- * must take no arguments.
- *
- * <p>The annotated method will be called after all tests, once per class.
- *
- * <p>The state prior to calling this method is not guaranteed, as test methods may have changed the
- * state. If any class-level annotation assumptions are violated this method will not be run.
- *
- * <p>If there are multiple methods annotated {@code @AfterClass} there is no guarantee as to the
- * order they will be executed.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface AfterClass {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
deleted file mode 100644
index 5f76ba6..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/BeforeClass.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Replacement for {@link org.junit.BeforeClass} for use by classes which use {@link DeviceState}.
- *
- * <p>Methods annotated {@link BeforeClass} must be public, static, must return {@code void}, and
- * must take no arguments.
- *
- * <p>The annotated method will be called before any tests, once per class. Class-level Bedstead
- * annotations will be processed before running the method, and if any assumptions are violated the
- * method will not be run.
- *
- * <p>If there are multiple methods annotated {@code @BeforeClass} there is no guarantee as to the
- * order they will be executed.
- */
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.METHOD)
-public @interface BeforeClass {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
deleted file mode 100644
index 3cb8104..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoSecondaryUser.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a device which has no secondary user that is not the
- * instrumented user.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which
- * has no secondary user that is not the current user. Otherwise, you can use {@link DeviceState}
- * to ensure that the device enters the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface EnsureHasNoSecondaryUser {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
deleted file mode 100644
index 50dac4d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoTvProfile.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has no Tv profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * no Tv profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoProfileAnnotation("com.android.tv.profile")
-public @interface EnsureHasNoTvProfile {
-    /** Which user type the tv profile should not be attached to. */
-    DeviceState.UserType forUser() default CURRENT_USER;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
deleted file mode 100644
index bec8a6b..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasNoWorkProfile.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasNoProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which does not have a work profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * no work profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoProfileAnnotation("android.os.usertype.profile.MANAGED")
-@RequireFeature("android.software.managed_users")
-public @interface EnsureHasNoWorkProfile {
-    /** Which user type the work profile should not be attached to. */
-    DeviceState.UserType forUser() default CURRENT_USER;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
deleted file mode 100644
index 186d86b..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasSecondaryUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a device which has a secondary user.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which
- * has a secondary user that is not the current user. Otherwise, you can use {@link DeviceState}
- * to ensure that the device enters the correct state for the method. If there is not already a
- * secondary user on the device, and the device does not support creating additional users, then
- * the test will be skipped.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface EnsureHasSecondaryUser {
-    /** Whether the instrumented test app should be installed in the secondary user. */
-    OptionalBoolean installInstrumentedApp() default TRUE;
-
-    /**
-     * Should we ensure that we are switched to the given user
-     */
-    OptionalBoolean switchedToUser() default ANY;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
deleted file mode 100644
index 35f3283..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasTvProfile.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has a Tv profile.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * a Tv profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasProfileAnnotation("com.android.tv.profile")
-public @interface EnsureHasTvProfile {
-    /** Which user type the tv profile should be attached to. */
-    DeviceState.UserType forUser() default CURRENT_USER;
-
-    /** Whether the instrumented test app should be installed in the tv profile. */
-    OptionalBoolean installInstrumentedApp() default TRUE;
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default ANY;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
deleted file mode 100644
index 892b7f4..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsureHasWorkProfile.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.meta.EnsureHasProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a user which has a work profile.
- *
- * <p>Use of this annotation implies
- * {@code RequireFeature("android.software.managed_users", SKIP)}.
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which has
- * a work profile. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
-@RequireFeature("android.software.managed_users")
-@EnsureHasNoDeviceOwner // TODO: This should only apply on Android R+
-public @interface EnsureHasWorkProfile {
-    /** Which user type the work profile should be attached to. */
-    DeviceState.UserType forUser() default CURRENT_USER;
-
-    /** Whether the instrumented test app should be installed in the work profile. */
-    OptionalBoolean installInstrumentedApp() default TRUE;
-
-    /**
-     * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
-     *
-     * <p>Only one device policy controller per test should be marked as primary.
-     */
-    boolean dpcIsPrimary() default false;
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default ANY;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
deleted file mode 100644
index 9ce07a7..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/EnsurePackageNotInstalled.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is not installed.
- *
- * <p>You can guarantee that these methods do not run on devices with the package by
- * using {@code DeviceState}.
- *
- * <p>Tests annotated with this will attempt to remove the package if it exists, or otherwise fail.
- * If you'd rather skip or fail tests immediately without attempting to remove see
- * {@link RequirePackageNotInstalled}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(EnsurePackagesNotInstalled.class)
-public @interface EnsurePackageNotInstalled {
-    String value();
-    DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default MIDDLE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
deleted file mode 100644
index 830fa10..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireAospBuild.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequirePackageNotInstalled(value = GMS_CORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageNotInstalled(value = PLAY_STORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageNotInstalled(value = GSF_PACKAGE, onUser = DeviceState.UserType.ANY)
-public @interface RequireAospBuild {
-    String GMS_CORE_PACKAGE = "com.google.android.gms";
-    String PLAY_STORE_PACKAGE = "com.android.vending";
-    String GSF_PACKAGE = "com.google.android.gsf";
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
deleted file mode 100644
index bbef533..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireDoesNotHaveFeatures.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.annotations.meta.RepeatingAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RepeatingAnnotation
-public @interface RequireDoesNotHaveFeatures {
-    RequireDoesNotHaveFeature[] value();
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
deleted file mode 100644
index 48e7984..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsBuild.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.GSF_PACKAGE;
-import static com.android.bedstead.harrier.annotations.RequireAospBuild.PLAY_STORE_PACKAGE;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequirePackageInstalled(value = GMS_CORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageInstalled(value = PLAY_STORE_PACKAGE, onUser = DeviceState.UserType.ANY)
-@RequirePackageInstalled(value = GSF_PACKAGE, onUser = DeviceState.UserType.ANY)
-public @interface RequireGmsBuild {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java
deleted file mode 100644
index 44cbeff..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireGmsInstrumentation.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should be run using GMS Instrumentation for certain versions.
- *
- * <p>This will apply {@link RequireSdkVersion} to ensure that on the given versions, this test
- * only runs when the instrumented package is `com.google.android.gms`. It will also skip the test
- * if it is run with gms instrumentation on a version which does not require it.
- *
- * <p>This allows for two test configurations to be set up, one which instruments GMS and one
- * which does not - and both pointed at the same test sources.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device with the
- * given state. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the sdk version is not correct.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireGmsInstrumentation {
-    int min() default 1;
-    int max() default Integer.MAX_VALUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
deleted file mode 100644
index 257cc2f..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireLowRamDevice.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to indicate that a test requires a low ram device.
- *
- * <p>This can be enforced by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireLowRamDevice {
-    String reason();
-    FailureMode failureMode() default FailureMode.SKIP;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
deleted file mode 100644
index 8d90920..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireNotLowRamDevice.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Annotation to indicate that a test does not run on low ram devices.
- *
- * <p>This can be enforced by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireNotLowRamDevice {
-    String reason();
-    FailureMode failureMode() default FailureMode.SKIP;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
deleted file mode 100644
index e2f3196..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageInstalled.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is installed.
- *
- * <p>You can guarantee that these methods do not run on devices lacking the package by
- * using {@code DeviceState}.
- *
- * <p>By default the test will be skipped if the package is not available. If you'd rather the test
- * fail then use {@code failureMode = FailureMode.FAIL}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequirePackagesInstalled.class)
-public @interface RequirePackageInstalled {
-    String value();
-    DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
-    FailureMode failureMode() default FailureMode.SKIP;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
deleted file mode 100644
index 36375c1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequirePackageNotInstalled.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run only when a given package is not installed.
- *
- * <p>You can guarantee that these methods do not run on devices with the package by
- * using {@code DeviceState}.
- *
- * <p>By default the test will be skipped if the package is available. If you'd rather the test
- * fail then use {@code failureMode = FailureMode.FAIL}. If you'd like to uninstall the package if
- * it is installed, see {@link EnsurePackageNotInstalled}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequirePackagesNotInstalled.class)
-public @interface RequirePackageNotInstalled {
-    String value();
-    DeviceState.UserType onUser() default DeviceState.UserType.CURRENT_USER;
-    FailureMode failureMode() default FailureMode.SKIP;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
deleted file mode 100644
index 446bb85..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunNotOnSecondaryUser.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a non-secondary user.
- *
- * <p>Your test configuration should be such that this test is only run where a non-secondary user
- * is created and the test is being run on that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run on a secondary user by
- * using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
- * requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-// To ensure that the test doesn't run on the secondary user we require that the test run on
-// the primary user to ensure consistent behaviour.
-@RequireRunOnPrimaryUser
-public @interface RequireRunNotOnSecondaryUser {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
deleted file mode 100644
index a58eb28..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnPrimaryUser.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on the primary user.
- *
- * <p>Your test configuration should be such that this test is only run on the primary user
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of the primary
- * user by using {@link DeviceState}.
- *
- * <p>Note that in practice this requires that the test runs on the system user, but excludes
- * headless system users. To mark that a test should run on the system user, including headless
- * system users, see {@link RequireRunOnSystemUser}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation("android.os.usertype.full.SYSTEM")
-public @interface RequireRunOnPrimaryUser {
-    /**
-     * Should we ensure that we are switched to the given user
-     */
-    OptionalBoolean switchedToUser() default TRUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
deleted file mode 100644
index b3df118..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSecondaryUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on a secondary user.
- *
- * <p>Your test configuration should be such that this test is only run where a secondary user is
- * created and the test is being run on that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a secondary user by
- * using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit
- * requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation("android.os.usertype.full.SECONDARY")
-public @interface RequireRunOnSecondaryUser {
-    /**
-     * Should we ensure that we are switched to the given user
-     */
-    OptionalBoolean switchedToUser() default TRUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
deleted file mode 100644
index 52c927e..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnSystemUser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnUserAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run on the system user.
- *
- * <p>Your test configuration should be such that this test is only run on the system user
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of the system
- * user by using {@link DeviceState}.
- *
- * <p>Note that this requires that the test runs on the system user, including headless system
- * users. To mark that a test should run on the primary user, excluding headless
- * system users, see {@link RequireRunOnPrimaryUser}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnUserAnnotation(
-        {"android.os.usertype.full.SYSTEM", "android.os.usertype.system.HEADLESS"})
-public @interface RequireRunOnSystemUser {
-    /**
-     * Should we ensure that we are switched to the given user
-     */
-    OptionalBoolean switchedToUser() default TRUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
deleted file mode 100644
index c2daad9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnTvProfile.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run within a Tv profile.
- *
- * <p>Your test configuration should be such that this test is only run where a Tv profile is
- * created and the test is being run within that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a Tv
- * profile by using {@link DeviceState}.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnProfileAnnotation("com.android.tv.profile")
-public @interface RequireRunOnTvProfile {
-    OptionalBoolean installInstrumentedAppInParent() default ANY;
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default TRUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
deleted file mode 100644
index b06a909..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireRunOnWorkProfile.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
-import com.android.bedstead.harrier.annotations.meta.RequireRunOnProfileAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should run within a work profile.
- *
- * <p>Your test configuration should be such that this test is only run where a work profile is
- * created and the test is being run within that user.
- *
- * <p>Optionally, you can guarantee that these methods do not run outside of a work
- * profile by using {@link DeviceState}.
- *
- * <p>This annotation by default opts a test into multi-user presubmit. New tests should also be
- * annotated {@link Postsubmit} until they are shown to meet the multi-user presubmit requirements.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireRunOnProfileAnnotation(value = "android.os.usertype.profile.MANAGED", hasProfileOwner = true)
-@EnsureHasProfileOwner
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface RequireRunOnWorkProfile {
-    OptionalBoolean installInstrumentedAppInParent() default ANY;
-
-    /**
-     * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
-     *
-     * <p>Only one device policy controller per test should be marked as primary.
-     */
-    boolean dpcIsPrimary() default false;
-
-    /**
-     * Affiliation ids to be set for the profile owner.
-     */
-    String[] affiliationIds() default {};
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default TRUE;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
deleted file mode 100644
index 8a272e9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireSdkVersion.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should only run on specified sdk versions.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device with the
- * given version. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the sdk version is not correct.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface RequireSdkVersion {
-    int min() default 1;
-    int max() default Integer.MAX_VALUE;
-    String reason() default "";
-    FailureMode failureMode() default FailureMode.SKIP;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
deleted file mode 100644
index 325447c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/RequireUserSupported.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.harrier.annotations;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.EARLY;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Repeatable;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test method should only run if a particular user type is supported
- *
- * <p>Your test configuration may be configured so that this test is only run on a user which
- * supports the user types. Otherwise, you can use {@link DeviceState} to ensure that the test is
- * not run when the user type is not supported.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@Repeatable(RequireUsersSupported.class)
-public @interface RequireUserSupported {
-    String value();
-    FailureMode failureMode() default FailureMode.SKIP;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default EARLY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
deleted file mode 100644
index c581561..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDelegate.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that the given admin delegates the given scope to a test app.
- *
- * <p>You should use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. You can use {@link DeviceState#delegate()} to interact with
- * the delegate.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasDelegate {
-
-    int ENSURE_HAS_DELEGATE_WEIGHT = DO_PO_WEIGHT + 1; // Should run after setting DO/PO
-
-    enum AdminType {
-        DEVICE_OWNER,
-        PROFILE_OWNER,
-        PRIMARY
-    }
-
-    /**
-     * The admin that should delegate this scope.
-     *
-     * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
-     * delegate will replace the primary dpc as primary without error.
-     */
-    AdminType admin();
-
-    /** The scope being delegated. */
-    String[] scopes();
-
-    /**
-     * Whether this delegate should be returned by calls to {@link DeviceState#policyManager()}.
-     *
-     * <p>Only one policy manager per test should be marked as primary.
-     */
-    boolean isPrimary() default false;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default ENSURE_HAS_DELEGATE_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
deleted file mode 100644
index 2fdfc9d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasDeviceOwner.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.MIDDLE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.FailureMode;
-import com.android.bedstead.harrier.annotations.RequireFeature;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that a device owner is available on the device.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * a device owner. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. If using {@link DeviceState}, you can use
- * {@link DeviceState#deviceOwner()} to interact with the device owner.
- *
- * <p>When running on a device with a headless system user, enforcing this with {@link DeviceState}
- * will also result in the profile owner of the current user being set to the same device policy
- * controller.
- *
- * <p>If {@link DeviceState} is required to set the device owner (because there isn't one already)
- * then all users and accounts may be removed from the device.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface EnsureHasDeviceOwner {
-
-    int DO_PO_WEIGHT = MIDDLE;
-
-     /** Behaviour if the device owner cannot be set. */
-    FailureMode failureMode() default FailureMode.FAIL;
-
-    /**
-     * Whether this DPC should be returned by calls to {@link DeviceState#dpc()} or
-     * {@link DeviceState#policyManager()}}.
-     *
-     * <p>Only one policy manager per test should be marked as primary.
-     */
-    boolean isPrimary() default false;
-
-    /**
-     * Affiliation ids to be set for the device owner.
-     */
-    String[] affiliationIds() default {};
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
deleted file mode 100644
index c0516f3..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDelegate.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.ENSURE_HAS_DELEGATE_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that the given admin does not delegate the given scope to a test app.
- *
- * <p>You should use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoDelegate {
-
-    enum AdminType {
-        DEVICE_OWNER,
-        PROFILE_OWNER,
-        PRIMARY,
-        ANY
-    }
-
-
-    /**
-     * The admin that should not delegate this scope.
-     *
-     * <p>Defaults to any admin
-     *
-     * <p>If this is set to {@link AdminType#PRIMARY} and {@link #isPrimary()} is true, then the
-     * delegate will replace the primary dpc as primary without error.
-     */
-    AdminType admin() default AdminType.ANY;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default ENSURE_HAS_DELEGATE_WEIGHT + 1; // Should run after setting delegate
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
deleted file mode 100644
index 307b554..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDeviceOwner.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no device owner on the device.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * no device owner. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoDeviceOwner {
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
deleted file mode 100644
index c97fd7f..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoDpc.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no dpc on the device.
- *
- * <p>This checks that there is no device owner, the current user has no work profiles, and the
- * current user has no profile owner.
- *
- * <p>Your test configuration may be configured so that this test is only run on a device which has
- * no dpc. Otherwise, you can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@EnsureHasNoDeviceOwner
-@EnsureHasNoWorkProfile
-@EnsureHasNoProfileOwner
-public @interface EnsureHasNoDpc {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
deleted file mode 100644
index 561da3c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasNoProfileOwner.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that there is no profile owner on the given user.
- *
- * <p>You can use {@link DeviceState} to ensure that the device enters the correct state for the
- * method.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-public @interface EnsureHasNoProfileOwner {
-    /** Which user type the profile owner should not be attached to. */
-    DeviceState.UserType onUser() default CURRENT_USER;
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default DO_PO_WEIGHT;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
deleted file mode 100644
index a408a4d..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/enterprise/EnsureHasProfileOwner.java
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * 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.harrier.annotations.enterprise;
-
-import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner.DO_PO_WEIGHT;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.RequireFeature;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Mark that a test requires that a profile owner is set.
- *
- * <p>You can use {@link DeviceState} to ensure that the device enters
- * the correct state for the method. If using {@link DeviceState}, you can use
- * {@link DeviceState#profileOwner()} to interact with the profile owner.
- */
-@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@RequireFeature(FEATURE_DEVICE_ADMIN)
-public @interface EnsureHasProfileOwner {
-    /** Which user type the work profile should be attached to. */
-    DeviceState.UserType onUser() default CURRENT_USER;
-
-    /**
-     * Whether this DPC should be returned by calls to {@link DeviceState#dpc()} or
-     * {@link DeviceState#policyManager()}}.
-     *
-     * <p>Only one policy manager per test should be marked as primary.
-     */
-    boolean isPrimary() default false;
-
-    /**
-     * Affiliation ids to be set for the profile owner.
-     */
-    String[] affiliationIds() default {};
-
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default DO_PO_WEIGHT;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
deleted file mode 100644
index fdd78f0..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasNoProfile.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-
-import com.android.bedstead.harrier.DeviceState;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a given user does not have a specific profile type.
- */
-@Target({})
-public @interface EnsureHasNoProfile {
-    DeviceState.UserType forUser() default CURRENT_USER;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
deleted file mode 100644
index b9b3e08..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/EnsureHasProfile.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * 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.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.DeviceState.UserType.CURRENT_USER;
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a given user has a specific profile type.
- */
-@Target({})
-public @interface EnsureHasProfile {
-    /** Which user type the profile should be attached to. */
-    DeviceState.UserType forUser() default CURRENT_USER;
-
-    /** Whether the test app should be installed in the profile. */
-    boolean installTestApp() default true;
-
-    /**
-     * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
-     *
-     * <p>Only one device policy controller per test should be marked as primary.
-     */
-    // NOTE: This field is only required if hasProfileOwner=true
-    boolean dpcIsPrimary() default false;
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default ANY;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
deleted file mode 100644
index 6bc4087..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/meta/RequireRunOnProfile.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.harrier.annotations.meta;
-
-import static com.android.bedstead.harrier.OptionalBoolean.ANY;
-import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.OptionalBoolean;
-
-import java.lang.annotation.Target;
-
-/**
- * This annotation should not be used directly. It exists as a template for annotations which
- * ensure that a test runs on a given profile type.
- */
-@Target({})
-public @interface RequireRunOnProfile {
-    OptionalBoolean installInstrumentedAppInParent() default ANY;
-
-    /**
-     * Whether the profile owner's DPC should be returned by calls to {@link DeviceState#dpc()}.
-     *
-     * <p>Only one device policy controller per test should be marked as primary.
-     */
-    // NOTE: This field is only required if hasProfileOwner=true
-    boolean dpcIsPrimary() default false;
-
-    /**
-     * Affiliation ids to be set for the profile owner.
-     */
-    // NOTE: This field is only required if hasProfileOwner=true
-    String[] affiliationIds() default {};
-
-    /**
-     * Should we ensure that we are switched to the parent of the profile.
-     */
-    OptionalBoolean switchedToParentUser() default TRUE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
deleted file mode 100644
index 17b5240..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnProfileOwnerPrimaryUser.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.harrier.annotations.parameterized;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
-
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Parameterize a test so that it runs on the primary user
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@ParameterizedAnnotation
-// Explicitly primary user which excludes headless users as this isn't a valid mode on headless
-@RequireRunOnPrimaryUser
-@EnsureHasNoDeviceOwner
-@EnsureHasProfileOwner(isPrimary = true)
-public @interface IncludeRunOnProfileOwnerPrimaryUser {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default LATE;
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
deleted file mode 100644
index 303e123..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/annotations/parameterized/IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.harrier.annotations.parameterized;
-
-import static com.android.bedstead.harrier.annotations.AnnotationRunPrecedence.LATE;
-
-import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.AnnotationRunPrecedence;
-import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
-import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
-import com.android.bedstead.harrier.annotations.meta.ParameterizedAnnotation;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Parameterize a test so that it runs on a device which has a primary and work profile, but runs
- * the test on a secondary user which is in a different profile group.
- */
-@Target({ElementType.METHOD, ElementType.TYPE})
-@Retention(RetentionPolicy.RUNTIME)
-@ParameterizedAnnotation
-@RequireRunOnSecondaryUser
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER, dpcIsPrimary = true)
-public @interface IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile {
-    /**
-     * Weight sets the order that annotations will be resolved.
-     *
-     * <p>Annotations with a lower weight will be resolved before annotations with a higher weight.
-     *
-     * <p>If there is an order requirement between annotations, ensure that the weight of the
-     * annotation which must be resolved first is lower than the one which must be resolved later.
-     *
-     * <p>Weight can be set to a {@link AnnotationRunPrecedence} constant, or to any {@link int}.
-     */
-    int weight() default LATE;
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
deleted file mode 100644
index 375ef7c..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/AccountManagement.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for account management tests.
- *
- * <p>This is used by {@link
- * DevicePolicyManager#setAccountManagementDisabled(ComponentName, String, boolean)} and
- * {@link DevicePolicyManager#getAccountTypesWithManagementDisabled()}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class AccountManagement {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
deleted file mode 100644
index 9e30dad..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictions.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_APP_RESTRICTIONS;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.Bundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setApplicationRestrictions(ComponentName, String, Bundle)} and
- * {@link DevicePolicyManager#getApplicationRestrictions(ComponentName, String)}.
- */
-@EnterprisePolicy(
-        dpc = {
-            APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND | CAN_BE_DELEGATED,
-            APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED},
-        delegatedScopes = DELEGATION_APP_RESTRICTIONS
-        )
-public final class ApplicationRestrictions {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
deleted file mode 100644
index 379b071..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ApplicationRestrictionsManagingPackage.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_IN_BACKGROUND;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.Bundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by the method
- * {@link DevicePolicyManager#setApplicationRestrictionsManagingPackage(ComponentName, String, Bundle)}
- */
-@EnterprisePolicy(
-        dpc = {
-                APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIES_IN_BACKGROUND,
-                APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class ApplicationRestrictionsManagingPackage {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
deleted file mode 100644
index 4a6d384..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CaCertManagement.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around installing/uninstalling CaCerts
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#installCaCert(ComponentName, byte[])} and
- * {@link DevicePolicyManager#uninstallCaCert(ComponentName, byte[])}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class CaCertManagement {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
deleted file mode 100644
index 4309473..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CameraPolicy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for application restrictions.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setCameraDisabled(ComponentName, boolean)} and
- * {@link DevicePolicyManager#getCameraDisabled(ComponentName)}.
- */
-// TODO(b/201753989):  Update the profileOwner flag once the behaviour of setCameraDisabled
-//  is properly defined on secondary user POs.
-@EnterprisePolicy(dpc = {
-        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_GLOBALLY,
-        APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER
-})
-public class CameraPolicy {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
deleted file mode 100644
index c1c62c1..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/CreateAndManageUser.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.os.PersistableBundle;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for create and manage user.
- *
- * <p>This is used by methods such as {@link DevicePolicyManager#createAndManageUser(
- * ComponentName, String, ComponentName, PersistableBundle, int)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
-public final class CreateAndManageUser {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
deleted file mode 100644
index c7d8f80..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/DefaultSmsApplication.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for set default SMS application test.
- *
- * <p>This is used by {@link DevicePolicyManager#setDefaultSmsApplication(ComponentName, String)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
-public final class DefaultSmsApplication {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
deleted file mode 100644
index 5bcde92..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/Delegation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for general admin delegation that doesn't have additional scope-specific constraints on
- * the admin type. Specific delegations with these constraints have their own policies.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class Delegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
deleted file mode 100644
index 2311c5e..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/EnrollmentSpecificId.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for testing enrollment specific ID.
- * See {@link DevicePolicyManager#getEnrollmentSpecificId()} for more detail.
- */
-@EnterprisePolicy(dpc = {
-        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER,
-        APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER
-})
-public final class EnrollmentSpecificId {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
deleted file mode 100644
index 9d409e6..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/KeyManagement.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.security.PrivateKey;
-import java.security.cert.Certificate;
-
-/**
- * Policies around key management
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#installKeyPair(ComponentName, PrivateKey, Certificate, String)} and
- * {@link DevicePolicyManager#removeKeyPair(ComponentName, String)}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
-        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public final class KeyManagement {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
deleted file mode 100644
index a4e8124..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/LockTask.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_AFFILIATED_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around Lock Task mode
- * (https://developer.android.com/work/dpc/dedicated-devices/lock-task-mode).
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setLockTaskFeatures(ComponentName, int)} and
- * {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
-                    APPLIED_BY_AFFILIATED_PROFILE_OWNER | APPLIED_BY_PROFILE_OWNER_USER_WITH_NO_DO | APPLIES_TO_OWN_USER})
-public final class LockTask {
-}
\ No newline at end of file
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
deleted file mode 100644
index ae4dfcd..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/NetworkLoggingDelegation.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for network logging delegation.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@link
- * DevicePolicyManager#DELEGATION_NETWORK_LOGGING}.
- */
-@EnterprisePolicy(dpc = {
-        APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER})
-public final class NetworkLoggingDelegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
deleted file mode 100644
index 45185a9..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/PreferentialNetworkService.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_PROFILE;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for testing preferential network service.
- * See {@link DevicePolicyManager#setPreferentialNetworkServiceEnabled(boolean)} for more detail.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_PROFILE_OWNER_PROFILE | APPLIES_TO_OWN_USER)
-public final class PreferentialNetworkService {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
deleted file mode 100644
index 82a30a8..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ResetPasswordWithToken.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around resetting a new device password
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#resetPasswordWithToken(ComponentName, String, byte[], int)}
- */
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER,
-        APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER})
-public class ResetPasswordWithToken {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
deleted file mode 100644
index e165663..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/ScreenCaptureDisabled.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for disabling screen capture.
- *
- * <p>Users of this policy are
- * {@link DevicePolicyManager#setScreenCaptureDisabled(ComponentName, boolean)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER
-        | APPLIED_BY_PARENT_INSTANCE_OF_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class ScreenCaptureDisabled {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
deleted file mode 100644
index b8f7617..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SecurityLoggingDelegation.java
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for security logging delegation.
- *
- * <p>This is used for methods such as {@link
- * DevicePolicyManager#setDelegatedScopes(ComponentName, String, List)} with scope {@link
- * DevicePolicyManager#DELEGATION_SECURITY_LOGGING}.
- */
-// TODO(b/198774281): COPE profile POs can call this too, but we need to add the flag.
-@EnterprisePolicy(dpc = {APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER})
-public final class SecurityLoggingDelegation {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
deleted file mode 100644
index bc3861a..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetPermissionGrantState.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a basic permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting permissions not covered by other policies.
- */
-@EnterprisePolicy(
-        dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER | CAN_BE_DELEGATED,
-        delegatedScopes = DELEGATION_PERMISSION_GRANT)
-public final class SetPermissionGrantState {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
deleted file mode 100644
index 9e1e832..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSensorPermissionGranted.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a sensor permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting sensor permissions.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER)
-public final class SetSensorPermissionGranted {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
deleted file mode 100644
index fac7dcd..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SetSmsPermissionGranted.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static android.app.admin.DevicePolicyManager.DELEGATION_PERMISSION_GRANT;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.CAN_BE_DELEGATED;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policies around setting the grant state of a SMS related permission.
- *
- * <p>This is used by
- * {@link DevicePolicyManager#setPermissionGrantState(ComponentName, String, String, int)} when
- * granting sms-related permissions.
- */
-// TODO(198311372): Check if APPLIED_BY_PROFILE_OWNER_USER is expected
-@EnterprisePolicy(
-        dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_TO_OWN_USER | APPLIED_BY_PROFILE_OWNER_USER | CAN_BE_DELEGATED,
-        delegatedScopes = DELEGATION_PERMISSION_GRANT)
-public final class SetSmsPermissionGranted {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
deleted file mode 100644
index b963290..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/SupportMessage.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_PROFILE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_TO_OWN_USER;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-/**
- * Policy for long and short support messages.
- *
- * <p>Users of this policy are {@link DevicePolicyManager#setLongSupportMessage(ComponentName,
- * CharSequence)}, {@link DevicePolicyManager#setShortSupportMessage(ComponentName, CharSequence)},
- * {@link DevicePolicyManager#getLongSupportMessage(ComponentName)} and {@link
- * DevicePolicyManager#getShortSupportMessage(ComponentName)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIED_BY_PROFILE_OWNER | APPLIES_TO_OWN_USER)
-public final class SupportMessage {
-}
diff --git a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java b/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
deleted file mode 100644
index 2adaf92..0000000
--- a/common/device-side/bedstead/harrier/src/main/java/com/android/bedstead/harrier/policies/UserControlDisabledPackages.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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.harrier.policies;
-
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIED_BY_DEVICE_OWNER;
-import static com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy.APPLIES_GLOBALLY;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-
-import com.android.bedstead.harrier.annotations.enterprise.EnterprisePolicy;
-
-import java.util.List;
-
-/**
- * Policy for user control disabled packages.
- *
- * <p>This is used by methods such as
- * {@link DevicePolicyManager#setUserControlDisabledPackages(ComponentName, List)} and
- * {@link DevicePolicyManager#getUserControlDisabledPackages(ComponentName)}.
- */
-@EnterprisePolicy(dpc = APPLIED_BY_DEVICE_OWNER | APPLIES_GLOBALLY)
-public final class UserControlDisabledPackages {
-}
diff --git a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java
new file mode 100644
index 0000000..ce008dd
--- /dev/null
+++ b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/BedsteadJUnit4Test.java
@@ -0,0 +1,99 @@
+/*
+ * 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.harrier;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.bedstead.harrier.annotations.AfterClass;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerPrimaryUser;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@RunWith(BedsteadJUnit4.class)
+public class BedsteadJUnit4Test {
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @StringTestParameter({"A", "B"})
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface TwoValuesStringTestParameter {
+
+    }
+
+    private static int sSimpleParameterizedCalls = 0;
+    private static int sMultipleSimpleParameterizedCalls = 0;
+    private static int sBedsteadParameterizedCalls = 0;
+    private static int sBedsteadPlusSimpleParameterizedCalls = 0;
+    private static int sIndirectParameterizedCalls = 0;
+    private static int sIntParameterizedCalls = 0;
+
+    @AfterClass
+    public static void afterClass() {
+        assertThat(sSimpleParameterizedCalls).isEqualTo(2);
+        assertThat(sMultipleSimpleParameterizedCalls).isEqualTo(4);
+        assertThat(sBedsteadParameterizedCalls).isEqualTo(2);
+        assertThat(sBedsteadPlusSimpleParameterizedCalls).isEqualTo(4);
+        assertThat(sIndirectParameterizedCalls).isEqualTo(2);
+        assertThat(sIntParameterizedCalls).isEqualTo(2);
+    }
+
+    @Test
+    @IncludeRunOnDeviceOwnerUser
+    @IncludeRunOnProfileOwnerPrimaryUser
+    public void bedsteadParameterized() {
+        sBedsteadParameterizedCalls += 1;
+    }
+
+    @Test
+    @IncludeRunOnDeviceOwnerUser
+    @IncludeRunOnProfileOwnerPrimaryUser
+    public void bedsteadPlusSimpleParameterized(@StringTestParameter({"A", "B"}) String argument) {
+        sBedsteadPlusSimpleParameterizedCalls += 1;
+    }
+
+    @Test
+    public void simpleParameterized(@StringTestParameter({"A", "B"}) String argument) {
+        sSimpleParameterizedCalls += 1;
+    }
+
+    @Test
+    public void multipleSimpleParameterized(
+            @StringTestParameter({"A", "B"}) String argument1,
+            @StringTestParameter({"C", "D"}) String argument2) {
+        sMultipleSimpleParameterizedCalls += 1;
+    }
+
+    @Test
+    public void indirectParameterized(@TwoValuesStringTestParameter String argument) {
+        sIndirectParameterizedCalls += 1;
+    }
+
+    @Test
+    public void intParameterized(@IntTestParameter({1, 2}) int argument) {
+        sIntParameterizedCalls += 1;
+    }
+}
diff --git a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
index ba3c2ef..a82f7db 100644
--- a/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
+++ b/common/device-side/bedstead/harrier/src/test/java/com/android/bedstead/harrier/DeviceStateTest.java
@@ -23,10 +23,10 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
-import static com.android.bedstead.harrier.DeviceState.UserType.ANY;
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
 import static com.android.bedstead.harrier.OptionalBoolean.FALSE;
 import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.ANY;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
 import static com.android.bedstead.harrier.annotations.RequireAospBuild.GMS_CORE_PACKAGE;
 import static com.android.bedstead.harrier.annotations.RequireCnGmsBuild.CHINA_GOOGLE_SERVICES_FEATURE;
 import static com.android.bedstead.harrier.annotations.enterprise.EnsureHasDelegate.AdminType.DEVICE_OWNER;
@@ -40,6 +40,7 @@
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManager;
+import android.app.admin.DevicePolicyManager;
 import android.os.Build;
 import android.os.Bundle;
 import android.platform.test.annotations.AppModeFull;
@@ -53,6 +54,8 @@
 import com.android.bedstead.harrier.annotations.EnsureHasTvProfile;
 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.annotations.EnsurePackageNotInstalled;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
 import com.android.bedstead.harrier.annotations.RequireAospBuild;
 import com.android.bedstead.harrier.annotations.RequireCnGmsBuild;
 import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
@@ -82,6 +85,7 @@
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnBackgroundDeviceOwnerUser;
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnDeviceOwnerUser;
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnNonAffiliatedDeviceOwnerSecondaryUser;
+import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerUsingParentInstance;
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnParentOfProfileOwnerWithNoDeviceOwner;
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnProfileOwnerProfileWithNoDeviceOwner;
 import com.android.bedstead.harrier.annotations.parameterized.IncludeRunOnSecondaryUserInDifferentProfileGroupToProfileOwnerProfile;
@@ -110,6 +114,11 @@
     private static final String TEST_PERMISSION_1 = INTERACT_ACROSS_PROFILES;
     private static final String TEST_PERMISSION_2 = INTERACT_ACROSS_USERS_FULL;
 
+    private static final DevicePolicyManager sLocalDevicePolicyManager =
+            TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
+
+    private static final long TIMEOUT = 4000000;
+
     @Test
     @EnsureHasWorkProfile
     public void workProfile_workProfileProvided_returnsWorkProfile() {
@@ -359,14 +368,14 @@
     }
 
     @EnsureHasSecondaryUser
-    @EnsureHasProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+    @EnsureHasProfileOwner(onUser = UserType.SECONDARY_USER)
     public void ensureHasProfileOwnerAnnotation_otherUser_setsProfileOwner() {
         assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser()))
                 .isNotNull();
     }
 
     @EnsureHasSecondaryUser
-    @EnsureHasNoProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+    @EnsureHasNoProfileOwner(onUser = UserType.SECONDARY_USER)
     public void ensureHasNoProfileOwnerAnnotation_otherUser_profileOwnerIsNotSet() {
         assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser())).isNull();
     }
@@ -385,14 +394,14 @@
     }
 
     @EnsureHasSecondaryUser
-    @EnsureHasProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+    @EnsureHasProfileOwner(onUser = UserType.SECONDARY_USER)
     public void profileOwner_otherUser_profileOwnerIsSet_returnsProfileOwner() {
         assertThat(sDeviceState.profileOwner(sDeviceState.secondaryUser())).isNotNull();
     }
 
     @Test
     @EnsureHasSecondaryUser
-    @EnsureHasNoProfileOwner(onUser = DeviceState.UserType.SECONDARY_USER)
+    @EnsureHasNoProfileOwner(onUser = UserType.SECONDARY_USER)
     public void profileOwner_otherUser_profileOwnerIsNotSet_throwsException() {
         assertThrows(IllegalStateException.class, sDeviceState::profileOwner);
     }
@@ -400,7 +409,7 @@
     @Test
     public void profileOwner_userType_onUserIsNull_throwsException() {
         assertThrows(NullPointerException.class,
-                () -> sDeviceState.profileOwner((DeviceState.UserType) null));
+                () -> sDeviceState.profileOwner((UserType) null));
     }
 
     @Test
@@ -775,4 +784,46 @@
     public void ensureHasDelegateAnnotation_primaryAdminAndReplace_dpcReturnsDelegate() {
         assertThat(sDeviceState.dpc()).isInstanceOf(RemoteDelegate.class);
     }
+
+    @Test
+    @EnsureScreenIsOn
+    public void ensureScreenIsOnAnnotation_screenIsOn() {
+        assertThat(TestApis.device().isScreenOn()).isTrue();
+    }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void requirePasswordNotSetAnnotation_passwordNotSet() {
+        assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+    }
+
+    @Test
+    @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+    public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_runningOnParentOfProfile() {
+        assertThat(sDeviceState.workProfile().parent()).isEqualTo(TestApis.users().instrumented());
+    }
+
+    @Test
+    @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+    public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_dpcIsOnProfile() {
+        assertThat(sDeviceState.dpc().user()).isEqualTo(sDeviceState.workProfile());
+    }
+
+    @Test
+    @IncludeRunOnParentOfProfileOwnerUsingParentInstance
+    public void includeRunOnParentOfProfileOwnerUsingProfileInstanceAnnotation_devicePolicyManagerAffectsParent() {
+        long previousRequiredStrongAuthTimeout =
+                sLocalDevicePolicyManager.getRequiredStrongAuthTimeout(/* admin= */ null);
+        try {
+            sDeviceState.dpc().devicePolicyManager()
+                    .setRequiredStrongAuthTimeout(sDeviceState.dpc().componentName(), TIMEOUT);
+
+            assertThat(sLocalDevicePolicyManager
+                    .getRequiredStrongAuthTimeout(/* admin= */ null)).isEqualTo(TIMEOUT);
+        } finally {
+            sDeviceState.dpc().devicePolicyManager()
+                    .setRequiredStrongAuthTimeout(
+                            sDeviceState.dpc().componentName(), previousRequiredStrongAuthTimeout);
+        }
+    }
 }
diff --git a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
index 5584ed4..46cc3fb 100644
--- a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
+++ b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/EnterpriseMetricInfo.java
@@ -27,6 +27,7 @@
     private final int mType;
     private final boolean mBoolean;
     private final List<String> mStrings;
+    private final int mInteger;
 
     EnterpriseMetricInfo(AtomsProto.DevicePolicyEvent event) {
         mAdminPackageName = event.adminPackageName;
@@ -34,24 +35,34 @@
         mBoolean = event.booleanValue;
         mStrings = (event.stringListValue == null) ? new ArrayList<>() : Arrays.asList(
                 event.stringListValue.stringValue);
+        mInteger = event.integerValue;
     }
 
+    /** Admin package name. */
     public String adminPackageName() {
         return mAdminPackageName;
     }
 
+    /** Type of metric. */
     public int type() {
         return mType;
     }
 
+    /** Arbitrary boolean value. */
     public boolean Boolean() {
         return mBoolean;
     }
 
+    /** Arbitrary list of strings. */
     public List<String> strings() {
         return mStrings;
     }
 
+    /** Arbitrary integer value. */
+    public int integer() {
+        return mInteger;
+    }
+
     @Override
     public String toString() {
         return "EnterpriseMetricInfo{"
@@ -59,6 +70,7 @@
                 + ", type=" + mType
                 + ", boolean=" + mBoolean
                 + ", strings=" + mStrings
+                + ", integer=" + mInteger
                 + "}";
     }
 }
diff --git a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
index 215a513..0b9c1dd 100644
--- a/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
+++ b/common/device-side/bedstead/metricsrecorder/src/main/java/com/android/bedstead/metricsrecorder/MetricQueryBuilder.java
@@ -43,11 +43,14 @@
             new BooleanQueryHelper<>(this);
     private final ListQueryHelper<MetricQueryBuilder, String, StringQuery<?>> mStringsQuery =
             new ListQueryHelper<>(this);
+    private final IntegerQueryHelper<MetricQueryBuilder> mIntegerQuery =
+            new IntegerQueryHelper<>(this);
 
     MetricQueryBuilder(EnterpriseMetricsRecorder recorder) {
         mRecorder = recorder;
     }
 
+    /** Query for {@link EnterpriseMetricInfo#type()}. */
     public IntegerQuery<MetricQueryBuilder> whereType() {
         if (hasStartedFetchingResults) {
             throw new IllegalStateException("Cannot modify query after fetching results");
@@ -55,6 +58,7 @@
         return mTypeQuery;
     }
 
+    /** Query for {@link EnterpriseMetricInfo#adminPackageName()}. */
     public StringQuery<MetricQueryBuilder> whereAdminPackageName() {
         if (hasStartedFetchingResults) {
             throw new IllegalStateException("Cannot modify query after fetching results");
@@ -62,6 +66,7 @@
         return mAdminPackageNameQuery;
     }
 
+    /** Query for {@link EnterpriseMetricInfo#Boolean()}. */
     public BooleanQuery<MetricQueryBuilder> whereBoolean() {
         if (hasStartedFetchingResults) {
             throw new IllegalStateException("Cannot modify query after fetching results");
@@ -69,6 +74,14 @@
         return mBooleanQuery;
     }
 
+    /** Query for {@link EnterpriseMetricInfo#integer()}. */
+    public IntegerQuery<MetricQueryBuilder> whereInteger() {
+        if (hasStartedFetchingResults) {
+            throw new IllegalStateException("Cannot modify query after fetching results");
+        }
+        return mIntegerQuery;
+    }
+
     public ListQueryHelper<MetricQueryBuilder, String, StringQuery<?>> whereStrings() {
         if (hasStartedFetchingResults) {
             throw new IllegalStateException("Cannot modify query after fetching results");
@@ -134,7 +147,8 @@
         return mAdminPackageNameQuery.matches(metric.adminPackageName())
                 && mTypeQuery.matches(metric.type())
                 && mBooleanQuery.matches(metric.Boolean())
-                && mStringsQuery.matches(metric.strings());
+                && mStringsQuery.matches(metric.strings())
+                && mIntegerQuery.matches(metric.integer());
     }
 
     @Override
diff --git a/common/device-side/bedstead/nene/Android.bp b/common/device-side/bedstead/nene/Android.bp
index 21809bb..7ca45b7 100644
--- a/common/device-side/bedstead/nene/Android.bp
+++ b/common/device-side/bedstead/nene/Android.bp
@@ -2,6 +2,22 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_library_host {
+    name: "NeneCommon",
+    srcs: [
+        "common/src/main/java/**/*.java"
+    ],
+}
+
+android_library {
+    name: "NeneCommonAndroid",
+    srcs: [
+        "common/src/main/java/**/*.java"
+    ],
+    manifest: "src/main/AndroidManifestInternal.xml",
+    min_sdk_version: "27"
+}
+
 // TODO(b/203507664): Remove NeneInternal once we no longer need QUERY_ALL_PACKAGES to install
 android_library {
     name: "NeneInternal",
@@ -14,6 +30,7 @@
         "compatibility-device-util-axt",
         "guava",
         "Queryable",
+        "NeneCommonAndroid",
         "RemoteFrameworkClasses"
     ],
     min_sdk_version: "27"
@@ -30,6 +47,7 @@
         "compatibility-device-util-axt",
         "guava",
         "Queryable",
+        "NeneCommonAndroid",
         "RemoteFrameworkClasses"
     ],
     min_sdk_version: "27"
diff --git a/common/device-side/bedstead/nene/AndroidTest.xml b/common/device-side/bedstead/nene/AndroidTest.xml
index f7a5151..c5601fa 100644
--- a/common/device-side/bedstead/nene/AndroidTest.xml
+++ b/common/device-side/bedstead/nene/AndroidTest.xml
@@ -15,6 +15,8 @@
   ~ limitations under the License.
   -->
 <configuration description="Config for Nene test cases">
+    <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+    <target_preparer class="com.android.tradefed.targetprep.RunOnSystemUserTargetPreparer"/>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="NeneTest.apk" />
@@ -24,6 +26,8 @@
         <option name="push" value="NeneTestApp1.apk->/data/local/tmp/NeneTestApp1.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
         <option name="package" value="com.android.bedstead.nene.test" />
     </test>
 </configuration>
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/common/AndroidManifest.xml b/common/device-side/bedstead/nene/common/AndroidManifest.xml
new file mode 100644
index 0000000..3ba12e1
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/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.nene.common">
+    <uses-sdk android:minSdkVersion="27" />
+    <application>
+    </application>
+</manifest>
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java
new file mode 100644
index 0000000..fb3ce77
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/annotations/Nullable.java
@@ -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.
+ */
+
+package com.android.bedstead.nene.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a value may be Null.
+ *
+ * <p>This can be used on host or device side code.
+ */
+@Target({ElementType.LOCAL_VARIABLE, ElementType.FIELD, ElementType.PARAMETER})
+@Retention(RetentionPolicy.SOURCE)
+public @interface Nullable {
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java
new file mode 100644
index 0000000..5927775
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/devicepolicy/CommonDevicePolicy.java
@@ -0,0 +1,59 @@
+/*
+ * 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.devicepolicy;
+
+/** Device Policy helper methods common to host and device. */
+public class CommonDevicePolicy {
+    CommonDevicePolicy() {
+
+    }
+
+    /** See {@code DevicePolicyManager#DELEGATION_CERT_INSTALL}. */
+    public static final String DELEGATION_CERT_INSTALL = "delegation-cert-install";
+
+    /** See {@code DevicePolicyManager#DELEGATION_APP_RESTRICTIONS}. */
+    public static final String DELEGATION_APP_RESTRICTIONS = "delegation-app-restrictions";
+
+    /** See {@code DevicePolicyManager#DELEGATION_BLOCK_UNINSTALL}. */
+    public static final String DELEGATION_BLOCK_UNINSTALL = "delegation-block-uninstall";
+
+    /** See {@code DevicePolicyManager#DELEGATION_PERMISSION_GRANT}. */
+    public static final String DELEGATION_PERMISSION_GRANT = "delegation-permission-grant";
+
+    /** See {@code DevicePolicyManager#DELEGATION_PACKAGE_ACCESS}. */
+    public static final String DELEGATION_PACKAGE_ACCESS = "delegation-package-access";
+
+    /** See {@code DevicePolicyManager#DELEGATION_ENABLE_SYSTEM_APP}. */
+    public static final String DELEGATION_ENABLE_SYSTEM_APP = "delegation-enable-system-app";
+
+    /** See {@code DevicePolicyManager#DELEGATION_INSTALL_EXISTING_PACKAGE}. */
+    public static final String DELEGATION_INSTALL_EXISTING_PACKAGE =
+            "delegation-install-existing-package";
+
+    /** See {@code DevicePolicyManager#DELEGATION_KEEP_UNINSTALLED_PACKAGES}. */
+    public static final String DELEGATION_KEEP_UNINSTALLED_PACKAGES =
+            "delegation-keep-uninstalled-packages";
+
+    /** See {@code DevicePolicyManager#DELEGATION_NETWORK_LOGGING}. */
+    public static final String DELEGATION_NETWORK_LOGGING = "delegation-network-logging";
+
+    /** See {@code DevicePolicyManager#DELEGATION_CERT_SELECTION}. */
+    public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
+
+    /** See {@code DevicePolicyManager#DELEGATION_SECURITY_LOGGING}. */
+    public static final String DELEGATION_SECURITY_LOGGING = "delegation-security-logging";
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
new file mode 100644
index 0000000..a3845f9
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
@@ -0,0 +1,85 @@
+/*
+ * 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.exceptions;
+
+import com.android.bedstead.nene.annotations.Nullable;
+
+/**
+ * An exception that gets thrown when interacting with Adb.
+ */
+public class AdbException extends Exception {
+
+    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.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) {
+        super(message, cause);
+        if (command == null) {
+            throw new NullPointerException();
+        }
+        this.mCommand = command;
+        this.mOutput = output;
+        this.mErr = null;
+    }
+
+    public String command() {
+        return mCommand;
+    }
+
+    public String output() {
+        return mOutput;
+    }
+
+    public String error() {
+        return mErr;
+    }
+
+    @Override
+    public String toString() {
+        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/exceptions/AdbParseException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/AdbParseException.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/NeneException.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/exceptions/PollValueFailedException.java
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java
new file mode 100644
index 0000000..6a6f622
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/packages/CommonPackages.java
@@ -0,0 +1,505 @@
+/*
+ * 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;
+
+/** Packages helper methods common to host and device. */
+public class CommonPackages {
+    CommonPackages() {}
+
+    /** See {@code PackageManager#FEATURE_AUDIO_LOW_LATENCY}. */
+    public static final String FEATURE_AUDIO_LOW_LATENCY = "android.hardware.audio.low_latency";
+
+    /** See {@code PackageManager#FEATURE_AUDIO_OUTPUT}. */
+    public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
+
+    /** See {@code PackageManager#FEATURE_AUDIO_PRO}. */
+    public static final String FEATURE_AUDIO_PRO = "android.hardware.audio.pro";
+
+    /** See {@code PackageManager#FEATURE_BLUETOOTH}. */
+    public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
+
+    /** See {@code PackageManager#FEATURE_BLUETOOTH_LE}. */
+    public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
+
+    /** See {@code PackageManager#FEATURE_CAMERA}. */
+    public static final String FEATURE_CAMERA = "android.hardware.camera";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_AUTOFOCUS}. */
+    public static final String FEATURE_CAMERA_AUTOFOCUS = "android.hardware.camera.autofocus";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_ANY}. */
+    public static final String FEATURE_CAMERA_ANY = "android.hardware.camera.any";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_EXTERNAL}. */
+    public static final String FEATURE_CAMERA_EXTERNAL = "android.hardware.camera.external";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_FLASH}. */
+    public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_FRONT}. */
+    public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_LEVEL_FULL}. */
+    public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR}. */
+    public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR =
+            "android.hardware.camera.capability.manual_sensor";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING}. */
+    public static final String FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING =
+            "android.hardware.camera.capability.manual_post_processing";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_CAPABILITY_RAW}. */
+    public static final String FEATURE_CAMERA_CAPABILITY_RAW =
+            "android.hardware.camera.capability.raw";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_AR}. */
+    public static final String FEATURE_CAMERA_AR =
+            "android.hardware.camera.ar";
+
+    /** See {@code PackageManager#FEATURE_CAMERA_CONCURRENT}. */
+    public static final String FEATURE_CAMERA_CONCURRENT = "android.hardware.camera.concurrent";
+
+    /** See {@code PackageManager#FEATURE_CONSUMER_IR}. */
+    public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
+
+    /** See {@code PackageManager#FEATURE_CONTEXT_HUB}. */
+    public static final String FEATURE_CONTEXT_HUB = "android.hardware.context_hub";
+
+    /** See {@code PackageManager#FEATURE_CTS}. */
+    public static final String FEATURE_CTS = "android.software.cts";
+
+    /** See {@code PackageManager#FEATURE_CAR_TEMPLATES_HOST}. */
+    public static final String FEATURE_CAR_TEMPLATES_HOST =
+            "android.software.car.templates_host";
+
+    /** See {@code PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE}. */
+    public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE =
+            "android.hardware.identity_credential";
+
+    /** See {@code PackageManager#FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS}. */
+    public static final String FEATURE_IDENTITY_CREDENTIAL_HARDWARE_DIRECT_ACCESS =
+            "android.hardware.identity_credential_direct_access";
+
+    /** See {@code PackageManager#FEATURE_LOCATION}. */
+    public static final String FEATURE_LOCATION = "android.hardware.location";
+
+    /** See {@code PackageManager#FEATURE_LOCATION_GPS}. */
+    public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
+
+    /** See {@code PackageManager#FEATURE_LOCATION_NETWORK}. */
+    public static final String FEATURE_LOCATION_NETWORK = "android.hardware.location.network";
+
+    /** See {@code PackageManager#FEATURE_FELICA}. */
+    public static final String FEATURE_FELICA = "android.hardware.felica";
+
+    /** See {@code PackageManager#FEATURE_RAM_LOW}. */
+    public static final String FEATURE_RAM_LOW = "android.hardware.ram.low";
+
+    /** See {@code PackageManager#FEATURE_RAM_NORMAL}. */
+    public static final String FEATURE_RAM_NORMAL = "android.hardware.ram.normal";
+
+    /** See {@code PackageManager#FEATURE_MICROPHONE}. */
+    public static final String FEATURE_MICROPHONE = "android.hardware.microphone";
+
+    /** See {@code PackageManager#FEATURE_NFC}. */
+    public static final String FEATURE_NFC = "android.hardware.nfc";
+
+    /** See {@code PackageManager#FEATURE_NFC_HCE}. */
+    public static final String FEATURE_NFC_HCE = "android.hardware.nfc.hce";
+
+    /** See {@code PackageManager#FEATURE_NFC_HOST_CARD_EMULATION}. */
+    public static final String FEATURE_NFC_HOST_CARD_EMULATION = "android.hardware.nfc.hce";
+
+    /** See {@code PackageManager#FEATURE_NFC_HOST_CARD_EMULATION_NFCF}. */
+    public static final String FEATURE_NFC_HOST_CARD_EMULATION_NFCF = "android.hardware.nfc.hcef";
+
+    /** See {@code PackageManager#FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC}. */
+    public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_UICC =
+            "android.hardware.nfc.uicc";
+
+    /** See {@code PackageManager#FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE}. */
+    public static final String FEATURE_NFC_OFF_HOST_CARD_EMULATION_ESE = "android.hardware.nfc.ese";
+
+    /** See {@code PackageManager#FEATURE_NFC_BEAM}. */
+    public static final String FEATURE_NFC_BEAM = "android.sofware.nfc.beam";
+
+    /** See {@code PackageManager#FEATURE_NFC_ANY}. */
+    public static final String FEATURE_NFC_ANY = "android.hardware.nfc.any";
+
+    /** See {@code PackageManager#FEATURE_SE_OMAPI_UICC}. */
+    public static final String FEATURE_SE_OMAPI_UICC = "android.hardware.se.omapi.uicc";
+
+    /** See {@code PackageManager#FEATURE_SE_OMAPI_ESE}. */
+    public static final String FEATURE_SE_OMAPI_ESE = "android.hardware.se.omapi.ese";
+
+    /** See {@code PackageManager#FEATURE_SE_OMAPI_SD}. */
+    public static final String FEATURE_SE_OMAPI_SD = "android.hardware.se.omapi.sd";
+
+    /** See {@code PackageManager#FEATURE_SECURITY_MODEL_COMPATIBLE}. */
+    public static final String FEATURE_SECURITY_MODEL_COMPATIBLE =
+            "android.hardware.security.model.compatible";
+
+    /** See {@code PackageManager#FEATURE_OPENGLES_EXTENSION_PACK}. */
+    public static final String FEATURE_OPENGLES_EXTENSION_PACK = "android.hardware.opengles.aep";
+
+    /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_LEVEL}. */
+    public static final String FEATURE_VULKAN_HARDWARE_LEVEL = "android.hardware.vulkan.level";
+
+    /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_COMPUTE}. */
+    public static final String FEATURE_VULKAN_HARDWARE_COMPUTE = "android.hardware.vulkan.compute";
+
+    /** See {@code PackageManager#FEATURE_VULKAN_HARDWARE_VERSION}. */
+    public static final String FEATURE_VULKAN_HARDWARE_VERSION = "android.hardware.vulkan.version";
+
+    /** See {@code PackageManager#FEATURE_VULKAN_DEQP_LEVEL}. */
+    public static final String FEATURE_VULKAN_DEQP_LEVEL = "android.software.vulkan.deqp.level";
+
+    /** See {@code PackageManager#FEATURE_OPENGLES_DEQP_LEVEL}. */
+    public static final String FEATURE_OPENGLES_DEQP_LEVEL = "android.software.opengles.deqp.level";
+
+    /** See {@code PackageManager#FEATURE_BROADCAST_RADIO}. */
+    public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+
+    /** See {@code PackageManager#FEATURE_SECURE_LOCK_SCREEN}. */
+    public static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_ACCELEROMETER}. */
+    public static final String FEATURE_SENSOR_ACCELEROMETER = "android.hardware.sensor.accelerometer";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_BAROMETER}. */
+    public static final String FEATURE_SENSOR_BAROMETER = "android.hardware.sensor.barometer";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_COMPASS}. */
+    public static final String FEATURE_SENSOR_COMPASS = "android.hardware.sensor.compass";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_GYROSCOPE}. */
+    public static final String FEATURE_SENSOR_GYROSCOPE = "android.hardware.sensor.gyroscope";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_LIGHT}. */
+    public static final String FEATURE_SENSOR_LIGHT = "android.hardware.sensor.light";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_PROXIMITY}. */
+    public static final String FEATURE_SENSOR_PROXIMITY = "android.hardware.sensor.proximity";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_STEP_COUNTER}. */
+    public static final String FEATURE_SENSOR_STEP_COUNTER = "android.hardware.sensor.stepcounter";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_STEP_DETECTOR}. */
+    public static final String FEATURE_SENSOR_STEP_DETECTOR = "android.hardware.sensor.stepdetector";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_HEART_RATE}. */
+    public static final String FEATURE_SENSOR_HEART_RATE = "android.hardware.sensor.heartrate";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_HEART_RATE_ECG}. */
+    public static final String FEATURE_SENSOR_HEART_RATE_ECG =
+            "android.hardware.sensor.heartrate.ecg";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_RELATIVE_HUMIDITY}. */
+    public static final String FEATURE_SENSOR_RELATIVE_HUMIDITY =
+            "android.hardware.sensor.relative_humidity";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_AMBIENT_TEMPERATURE}. */
+    public static final String FEATURE_SENSOR_AMBIENT_TEMPERATURE =
+            "android.hardware.sensor.ambient_temperature";
+
+    /** See {@code PackageManager#FEATURE_SENSOR_HINGE_ANGLE}. */
+    public static final String FEATURE_SENSOR_HINGE_ANGLE = "android.hardware.sensor.hinge_angle";
+
+    /** See {@code PackageManager#FEATURE_HIFI_SENSORS}. */
+    public static final String FEATURE_HIFI_SENSORS =
+            "android.hardware.sensor.hifi_sensors";
+
+    /** See {@code PackageManager#FEATURE_ASSIST_GESTURE}. */
+    public static final String FEATURE_ASSIST_GESTURE = "android.hardware.sensor.assist";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY}. */
+    public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_CDMA}. */
+    public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_GSM}. */
+    public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_CARRIERLOCK}. */
+    public static final String FEATURE_TELEPHONY_CARRIERLOCK =
+            "android.hardware.telephony.carrierlock";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_EUICC}. */
+    public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_MBMS}. */
+    public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_IMS}. */
+    public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+    /** See {@code PackageManager#FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION}. */
+    public static final String FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION =
+            "android.hardware.telephony.ims.singlereg";
+
+    /** See {@code PackageManager#FEATURE_UWB}. */
+    public static final String FEATURE_UWB = "android.hardware.uwb";
+
+    /** See {@code PackageManager#FEATURE_USB_HOST}. */
+    public static final String FEATURE_USB_HOST = "android.hardware.usb.host";
+
+    /** See {@code PackageManager#FEATURE_USB_ACCESSORY}. */
+    public static final String FEATURE_USB_ACCESSORY = "android.hardware.usb.accessory";
+
+    /** See {@code PackageManager#FEATURE_SIP}. */
+    public static final String FEATURE_SIP = "android.software.sip";
+
+    /** See {@code PackageManager#FEATURE_SIP_VOIP}. */
+    public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
+
+    /** See {@code PackageManager#FEATURE_CONNECTION_SERVICE}. */
+    public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+
+    /** See {@code PackageManager#FEATURE_TOUCHSCREEN}. */
+    public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
+
+    /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH}. */
+    public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
+
+    /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT}. */
+    public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT = "android.hardware.touchscreen.multitouch.distinct";
+
+    /** See {@code PackageManager#FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND}. */
+    public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand";
+
+    /** See {@code PackageManager#FEATURE_FAKETOUCH}. */
+    public static final String FEATURE_FAKETOUCH = "android.hardware.faketouch";
+
+    /** See {@code PackageManager#FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT}. */
+    public static final String FEATURE_FAKETOUCH_MULTITOUCH_DISTINCT = "android.hardware.faketouch.multitouch.distinct";
+
+    /** See {@code PackageManager#FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND}. */
+    public static final String FEATURE_FAKETOUCH_MULTITOUCH_JAZZHAND = "android.hardware.faketouch.multitouch.jazzhand";
+
+    /** See {@code PackageManager#FEATURE_FINGERPRINT}. */
+    public static final String FEATURE_FINGERPRINT = "android.hardware.fingerprint";
+
+    /** See {@code PackageManager#FEATURE_FACE}. */
+    public static final String FEATURE_FACE = "android.hardware.biometrics.face";
+
+    /** See {@code PackageManager#FEATURE_IRIS}. */
+    public static final String FEATURE_IRIS = "android.hardware.biometrics.iris";
+
+    /** See {@code PackageManager#FEATURE_SCREEN_PORTRAIT}. */
+    public static final String FEATURE_SCREEN_PORTRAIT = "android.hardware.screen.portrait";
+
+    /** See {@code PackageManager#FEATURE_SCREEN_LANDSCAPE}. */
+    public static final String FEATURE_SCREEN_LANDSCAPE = "android.hardware.screen.landscape";
+
+    /** See {@code PackageManager#FEATURE_LIVE_WALLPAPER}. */
+    public static final String FEATURE_LIVE_WALLPAPER = "android.software.live_wallpaper";
+
+    /** See {@code PackageManager#FEATURE_APP_WIDGETS}. */
+    public static final String FEATURE_APP_WIDGETS = "android.software.app_widgets";
+
+    /** See {@code PackageManager#FEATURE_CANT_SAVE_STATE}. */
+    public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
+
+    /** See {@code PackageManager#FEATURE_GAME_SERVICE}. */
+    public static final String FEATURE_GAME_SERVICE = "android.software.game_service";
+
+    /** See {@code PackageManager#FEATURE_VOICE_RECOGNIZERS}. */
+    public static final String FEATURE_VOICE_RECOGNIZERS = "android.software.voice_recognizers";
+
+    /** See {@code PackageManager#FEATURE_HOME_SCREEN}. */
+    public static final String FEATURE_HOME_SCREEN = "android.software.home_screen";
+
+    /** See {@code PackageManager#FEATURE_INPUT_METHODS}. */
+    public static final String FEATURE_INPUT_METHODS = "android.software.input_methods";
+
+    /** See {@code PackageManager#FEATURE_DEVICE_ADMIN}. */
+    public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
+
+    /** See {@code PackageManager#FEATURE_LEANBACK}. */
+    public static final String FEATURE_LEANBACK = "android.software.leanback";
+
+    /** See {@code PackageManager#FEATURE_LEANBACK_ONLY}. */
+    public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
+
+    /** See {@code PackageManager#FEATURE_LIVE_TV}. */
+    public static final String FEATURE_LIVE_TV = "android.software.live_tv";
+
+    /** See {@code PackageManager#FEATURE_WIFI}. */
+    public static final String FEATURE_WIFI = "android.hardware.wifi";
+
+    /** See {@code PackageManager#FEATURE_WIFI_DIRECT}. */
+    public static final String FEATURE_WIFI_DIRECT = "android.hardware.wifi.direct";
+
+    /** See {@code PackageManager#FEATURE_WIFI_AWARE}. */
+    public static final String FEATURE_WIFI_AWARE = "android.hardware.wifi.aware";
+
+    /** See {@code PackageManager#FEATURE_WIFI_PASSPOINT}. */
+    public static final String FEATURE_WIFI_PASSPOINT = "android.hardware.wifi.passpoint";
+
+    /** See {@code PackageManager#FEATURE_WIFI_RTT}. */
+    public static final String FEATURE_WIFI_RTT = "android.hardware.wifi.rtt";
+
+    /** See {@code PackageManager#FEATURE_LOWPAN}. */
+    public static final String FEATURE_LOWPAN = "android.hardware.lowpan";
+
+    /** See {@code PackageManager#FEATURE_AUTOMOTIVE}. */
+    public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    /** See {@code PackageManager#FEATURE_TELEVISION}. */
+    public static final String FEATURE_TELEVISION = "android.hardware.type.television";
+
+    /** See {@code PackageManager#FEATURE_WATCH}. */
+    public static final String FEATURE_WATCH = "android.hardware.type.watch";
+
+    /** See {@code PackageManager#FEATURE_EMBEDDED}. */
+    public static final String FEATURE_EMBEDDED = "android.hardware.type.embedded";
+
+    /** See {@code PackageManager#FEATURE_PC}. */
+    public static final String FEATURE_PC = "android.hardware.type.pc";
+
+    /** See {@code PackageManager#FEATURE_PRINTING}. */
+    public static final String FEATURE_PRINTING = "android.software.print";
+
+    /** See {@code PackageManager#FEATURE_COMPANION_DEVICE_SETUP}. */
+    public static final String FEATURE_COMPANION_DEVICE_SETUP =
+            "android.software.companion_device_setup";
+
+    /** See {@code PackageManager#FEATURE_BACKUP}. */
+    public static final String FEATURE_BACKUP = "android.software.backup";
+
+    /** See {@code PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT}. */
+    public static final String FEATURE_FREEFORM_WINDOW_MANAGEMENT =
+            "android.software.freeform_window_management";
+
+    /** See {@code PackageManager#FEATURE_PICTURE_IN_PICTURE}. */
+    public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
+
+    /** See {@code PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}. */
+    public static final String FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS =
+            "android.software.activities_on_secondary_displays";
+
+    /** See {@code PackageManager#FEATURE_MANAGED_USERS}. */
+    public static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
+
+    /** See {@code PackageManager#FEATURE_MANAGED_PROFILES}. */
+    public static final String FEATURE_MANAGED_PROFILES = "android.software.managed_users";
+
+    /** See {@code PackageManager#FEATURE_NEARBY}. */
+    public static final String FEATURE_NEARBY = "android.software.nearby";
+
+    /** See {@code PackageManager#FEATURE_VERIFIED_BOOT}. */
+    public static final String FEATURE_VERIFIED_BOOT = "android.software.verified_boot";
+
+    /** See {@code PackageManager#FEATURE_SECURELY_REMOVES_USERS}. */
+    public static final String FEATURE_SECURELY_REMOVES_USERS =
+            "android.software.securely_removes_users";
+
+    /** See {@code PackageManager#FEATURE_FILE_BASED_ENCRYPTION}. */
+    public static final String FEATURE_FILE_BASED_ENCRYPTION =
+            "android.software.file_based_encryption";
+
+    /** See {@code PackageManager#FEATURE_ADOPTABLE_STORAGE}. */
+    public static final String FEATURE_ADOPTABLE_STORAGE =
+            "android.software.adoptable_storage";
+
+    /** See {@code PackageManager#FEATURE_WEBVIEW}. */
+    public static final String FEATURE_WEBVIEW = "android.software.webview";
+
+    /** See {@code PackageManager#FEATURE_ETHERNET}. */
+    public static final String FEATURE_ETHERNET = "android.hardware.ethernet";
+
+    /** See {@code PackageManager#FEATURE_HDMI_CEC}. */
+    public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
+
+    /** See {@code PackageManager#FEATURE_GAMEPAD}. */
+    public static final String FEATURE_GAMEPAD = "android.hardware.gamepad";
+
+    /** See {@code PackageManager#FEATURE_MIDI}. */
+    public static final String FEATURE_MIDI = "android.software.midi";
+
+    /** See {@code PackageManager#FEATURE_VR_MODE}. */
+    public static final String FEATURE_VR_MODE = "android.software.vr.mode";
+
+    /** See {@code PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE}. */
+    public static final String FEATURE_VR_MODE_HIGH_PERFORMANCE
+            = "android.hardware.vr.high_performance";
+
+    /** See {@code PackageManager#FEATURE_AUTOFILL}. */
+    public static final String FEATURE_AUTOFILL = "android.software.autofill";
+
+    /** See {@code PackageManager#FEATURE_VR_HEADTRACKING}. */
+    public static final String FEATURE_VR_HEADTRACKING = "android.hardware.vr.headtracking";
+
+    /** See {@code PackageManager#FEATURE_HARDWARE_KEYSTORE}. */
+    public static final String FEATURE_HARDWARE_KEYSTORE = "android.hardware.hardware_keystore";
+
+    /** See {@code PackageManager#FEATURE_STRONGBOX_KEYSTORE}. */
+    public static final String FEATURE_STRONGBOX_KEYSTORE =
+            "android.hardware.strongbox_keystore";
+
+    /** See {@code PackageManager#FEATURE_SLICES_DISABLED}. */
+    public static final String FEATURE_SLICES_DISABLED = "android.software.slices_disabled";
+
+    /** See {@code PackageManager#FEATURE_DEVICE_UNIQUE_ATTESTATION}. */
+    public static final String FEATURE_DEVICE_UNIQUE_ATTESTATION =
+            "android.hardware.device_unique_attestation";
+
+    /** See {@code PackageManager#FEATURE_DEVICE_ID_ATTESTATION}. */
+    public static final String FEATURE_DEVICE_ID_ATTESTATION =
+            "android.software.device_id_attestation";
+
+    /** See {@code PackageManager#FEATURE_IPSEC_TUNNELS}. */
+    public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
+
+    /** See {@code PackageManager#FEATURE_CONTROLS}. */
+    public static final String FEATURE_CONTROLS = "android.software.controls";
+
+    /** See {@code PackageManager#FEATURE_REBOOT_ESCROW}. */
+    public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+    /** See {@code PackageManager#FEATURE_INCREMENTAL_DELIVERY}. */
+    public static final String FEATURE_INCREMENTAL_DELIVERY =
+            "android.software.incremental_delivery";
+
+    /** See {@code PackageManager#FEATURE_TUNER}. */
+    public static final String FEATURE_TUNER = "android.hardware.tv.tuner";
+
+    /** See {@code PackageManager#FEATURE_APP_ENUMERATION}. */
+    public static final String FEATURE_APP_ENUMERATION = "android.software.app_enumeration";
+
+    /** See {@code PackageManager#FEATURE_KEYSTORE_SINGLE_USE_KEY}. */
+    public static final String FEATURE_KEYSTORE_SINGLE_USE_KEY =
+            "android.hardware.keystore.single_use_key";
+
+    /** See {@code PackageManager#FEATURE_KEYSTORE_LIMITED_USE_KEY}. */
+    public static final String FEATURE_KEYSTORE_LIMITED_USE_KEY =
+            "android.hardware.keystore.limited_use_key";
+
+    /** See {@code PackageManager#FEATURE_KEYSTORE_APP_ATTEST_KEY}. */
+    public static final String FEATURE_KEYSTORE_APP_ATTEST_KEY =
+            "android.hardware.keystore.app_attest_key";
+
+    /** See {@code PackageManager#FEATURE_APP_COMPAT_OVERRIDES}. */
+    public static final String FEATURE_APP_COMPAT_OVERRIDES =
+            "android.software.app_compat_overrides";
+
+    /** See {@code PackageManager#FEATURE_COMMUNAL_MODE}. */
+    public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
+}
diff --git a/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java
new file mode 100644
index 0000000..69d191c
--- /dev/null
+++ b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/permissions/CommonPermissions.java
@@ -0,0 +1,1845 @@
+/*
+ * 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.permissions;
+
+/** Permissions helper methods common to host and device. */
+public class CommonPermissions {
+
+    CommonPermissions() {
+
+    }
+
+    /** See {@code Manifest#READ_CONTACTS} */
+    public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
+
+    /** See {@code Manifest#WRITE_CONTACTS} */
+    public static final String WRITE_CONTACTS = "android.permission.WRITE_CONTACTS";
+
+    /** See {@code Manifest#SET_DEFAULT_ACCOUNT_FOR_CONTACTS} */
+    public static final String SET_DEFAULT_ACCOUNT_FOR_CONTACTS =
+            "android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS";
+
+    /** See {@code Manifest#READ_CALENDAR} */
+    public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
+
+    /** See {@code Manifest#WRITE_CALENDAR} */
+    public static final String WRITE_CALENDAR = "android.permission.WRITE_CALENDAR";
+
+    /** See {@code Manifest#ACCESS_MESSAGES_ON_ICC} */
+    public static final String ACCESS_MESSAGES_ON_ICC = "android.permission"
+            + ".ACCESS_MESSAGES_ON_ICC";
+
+    /** See {@code Manifest#SEND_SMS} */
+    public static final String SEND_SMS = "android.permission.SEND_SMS";
+
+    /** See {@code Manifest#RECEIVE_SMS} */
+    public static final String RECEIVE_SMS = "android.permission.RECEIVE_SMS";
+
+    /** See {@code Manifest#READ_SMS} */
+    public static final String READ_SMS = "android.permission.READ_SMS";
+
+    /** See {@code Manifest#RECEIVE_WAP_PUSH} */
+    public static final String RECEIVE_WAP_PUSH = "android.permission.RECEIVE_WAP_PUSH";
+
+    /** See {@code Manifest#RECEIVE_MMS} */
+    public static final String RECEIVE_MMS = "android.permission.RECEIVE_MMS";
+
+    /** See {@code Manifest#BIND_CELL_BROADCAST_SERVICE} */
+    public static final String BIND_CELL_BROADCAST_SERVICE = "android.permission"
+            + ".BIND_CELL_BROADCAST_SERVICE";
+
+    /** See {@code Manifest#READ_CELL_BROADCASTS} */
+    public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
+
+    /** See {@code Manifest#WRITE_EXTERNAL_STORAGE} */
+    public static final String WRITE_EXTERNAL_STORAGE = "android.permission.WRITE_EXTERNAL_STORAGE";
+
+    /** See {@code Manifest#ACCESS_MEDIA_LOCATION} */
+    public static final String ACCESS_MEDIA_LOCATION = "android.permission.ACCESS_MEDIA_LOCATION";
+
+    /** See {@code Manifest#WRITE_OBB} */
+    public static final String WRITE_OBB = "android.permission.WRITE_OBB";
+
+    /** See {@code Manifest#MANAGE_EXTERNAL_STORAGE} */
+    public static final String MANAGE_EXTERNAL_STORAGE = "android.permission"
+            + ".MANAGE_EXTERNAL_STORAGE";
+
+    /** See {@code Manifest#MANAGE_MEDIA} */
+    public static final String MANAGE_MEDIA = "android.permission.MANAGE_MEDIA";
+
+    /** See {@code Manifest#ACCESS_FINE_LOCATION} */
+    public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+
+    /** See {@code Manifest#ACCESS_COARSE_LOCATION} */
+    public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+
+    /** See {@code Manifest#ACCESS_BACKGROUND_LOCATION} */
+    public static final String ACCESS_BACKGROUND_LOCATION =
+            "android.permission.ACCESS_BACKGROUND_LOCATION";
+
+    /** See {@code Manifest#ACCESS_IMS_CALL_SERVICE} */
+    public static final String ACCESS_IMS_CALL_SERVICE = "android.permission"
+            + ".ACCESS_IMS_CALL_SERVICE";
+
+    /** See {@code Manifest#PERFORM_IMS_SINGLE_REGISTRATION} */
+    public static final String PERFORM_IMS_SINGLE_REGISTRATION = "android.permission"
+            + ".PERFORM_IMS_SINGLE_REGISTRATION";
+
+    /** See {@code Manifest#READ_CALL_LOG} */
+    public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
+
+    /** See {@code Manifest#PROCESS_OUTGOING_CALLS} */
+    public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
+
+    /** See {@code Manifest#READ_PHONE_STATE} */
+    public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+
+    /** See {@code Manifest#READ_BASIC_PHONE_STATE} */
+    public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
+
+    /** See {@code Manifest#READ_PHONE_NUMBERS} */
+    public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
+
+    /** See {@code Manifest#CALL_PHONE} */
+    public static final String CALL_PHONE = "android.permission.CALL_PHONE";
+
+    /** See {@code Manifest#ADD_VOICEMAIL} */
+    public static final String ADD_VOICEMAIL = "com.android.voicemail.permission.ADD_VOICEMAIL";
+
+    /** See {@code Manifest#USE_SIP} */
+    public static final String USE_SIP = "android.permission.USE_SIP";
+
+    /** See {@code Manifest#ANSWER_PHONE_CALLS} */
+    public static final String ANSWER_PHONE_CALLS = "android.permission.ANSWER_PHONE_CALLS";
+
+    /** See {@code Manifest#MANAGE_OWN_CALLS} */
+    public static final String MANAGE_OWN_CALLS = "android.permission.MANAGE_OWN_CALLS";
+
+    /** See {@code Manifest#CALL_COMPANION_APP} */
+    public static final String CALL_COMPANION_APP = "android.permission.CALL_COMPANION_APP";
+
+    /** See {@code Manifest#EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS} */
+    public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission"
+            + ".EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
+
+    /** See {@code Manifest#ACCEPT_HANDOVER} */
+    public static final String ACCEPT_HANDOVER = "android.permission.ACCEPT_HANDOVER";
+
+    /** See {@code Manifest#RECORD_AUDIO} */
+    public static final String RECORD_AUDIO = "android.permission.RECORD_AUDIO";
+
+    /** See {@code Manifest#RECORD_BACKGROUND_AUDIO} */
+    public static final String RECORD_BACKGROUND_AUDIO =
+            "android.permission.RECORD_BACKGROUND_AUDIO";
+
+    /** See {@code Manifest#ACTIVITY_RECOGNITION} */
+    public static final String ACTIVITY_RECOGNITION = "android.permission.ACTIVITY_RECOGNITION";
+
+    /** See {@code Manifest#ACCESS_UCE_PRESENCE_SERVICE} */
+    public static final String ACCESS_UCE_PRESENCE_SERVICE =
+            "android.permission.ACCESS_UCE_PRESENCE_SERVICE";
+
+    /** See {@code Manifest#ACCESS_UCE_OPTIONS_SERVICE} */
+    public static final String ACCESS_UCE_OPTIONS_SERVICE =
+            "android.permission.ACCESS_UCE_OPTIONS_SERVICE";
+
+    /** See {@code Manifest#CAMERA} */
+    public static final String CAMERA = "android.permission.CAMERA";
+
+    /** See {@code Manifest#BACKGROUND_CAMERA} */
+    public static final String BACKGROUND_CAMERA = "android.permission.BACKGROUND_CAMERA";
+
+    /** See {@code Manifest#SYSTEM_CAMERA} */
+    public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
+
+    /** See {@code Manifest#CAMERA_OPEN_CLOSE_LISTENER} */
+    public static final String CAMERA_OPEN_CLOSE_LISTENER = "android.permission"
+            + ".CAMERA_OPEN_CLOSE_LISTENER";
+
+    /** See {@code Manifest#HIGH_SAMPLING_RATE_SENSORS} */
+    public static final String HIGH_SAMPLING_RATE_SENSORS =
+            "android.permission.HIGH_SAMPLING_RATE_SENSORS";
+
+    /** See {@code Manifest#BODY_SENSORS} */
+    public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+
+    /** See {@code Manifest#USE_FINGERPRINT} */
+    public static final String USE_FINGERPRINT = "android.permission.USE_FINGERPRINT";
+
+    /** See {@code Manifest#USE_BIOMETRIC} */
+    public static final String USE_BIOMETRIC = "android.permission.USE_BIOMETRIC";
+
+    /** See {@code Manifest#POST_NOTIFICATIONS} */
+    public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
+
+    /** See {@code Manifest#READ_PROFILE} */
+    public static final String READ_PROFILE = "android.permission.READ_PROFILE";
+
+    /** See {@code Manifest#WRITE_PROFILE} */
+    public static final String WRITE_PROFILE = "android.permission.WRITE_PROFILE";
+
+    /** See {@code Manifest#READ_SOCIAL_STREAM} */
+    public static final String READ_SOCIAL_STREAM = "android.permission.READ_SOCIAL_STREAM";
+
+    /** See {@code Manifest#WRITE_SOCIAL_STREAM} */
+    public static final String WRITE_SOCIAL_STREAM = "android.permission.WRITE_SOCIAL_STREAM";
+
+    /** See {@code Manifest#READ_USER_DICTIONARY} */
+    public static final String READ_USER_DICTIONARY = "android.permission.READ_USER_DICTIONARY";
+
+    /** See {@code Manifest#WRITE_USER_DICTIONARY} */
+    public static final String WRITE_USER_DICTIONARY = "android.permission.WRITE_USER_DICTIONARY";
+
+    /** See {@code Manifest#WRITE_SMS} */
+    public static final String WRITE_SMS = "android.permission.WRITE_SMS";
+
+    /** See {@code Manifest#READ_HISTORY_BOOKMARKS} */
+    public static final String READ_HISTORY_BOOKMARKS =
+            "com.android.browser.permission.READ_HISTORY_BOOKMARKS";
+
+    /** See {@code Manifest#WRITE_HISTORY_BOOKMARKS} */
+    public static final String WRITE_HISTORY_BOOKMARKS =
+            "com.android.browser.permission.WRITE_HISTORY_BOOKMARKS";
+
+    /** See {@code Manifest#AUTHENTICATE_ACCOUNTS} */
+    public static final String AUTHENTICATE_ACCOUNTS = "android.permission.AUTHENTICATE_ACCOUNTS";
+
+    /** See {@code Manifest#MANAGE_ACCOUNTS} */
+    public static final String MANAGE_ACCOUNTS = "android.permission.MANAGE_ACCOUNTS";
+
+    /** See {@code Manifest#USE_CREDENTIALS} */
+    public static final String USE_CREDENTIALS = "android.permission.USE_CREDENTIALS";
+
+    /** See {@code Manifest#SUBSCRIBED_FEEDS_READ} */
+    public static final String SUBSCRIBED_FEEDS_READ = "android.permission.SUBSCRIBED_FEEDS_READ";
+
+    /** See {@code Manifest#SUBSCRIBED_FEEDS_WRITE} */
+    public static final String SUBSCRIBED_FEEDS_WRITE = "android.permission"
+            + ".SUBSCRIBED_FEEDS_WRITE";
+
+    /** See {@code Manifest#FLASHLIGHT} */
+    public static final String FLASHLIGHT = "android.permission.FLASHLIGHT";
+
+    /** See {@code Manifest#SEND_RESPOND_VIA_MESSAGE} */
+    public static final String SEND_RESPOND_VIA_MESSAGE =
+            "android.permission.SEND_RESPOND_VIA_MESSAGE";
+
+    /** See {@code Manifest#SEND_SMS_NO_CONFIRMATION} */
+    public static final String SEND_SMS_NO_CONFIRMATION = "android.permission"
+            + ".SEND_SMS_NO_CONFIRMATION";
+
+    /** See {@code Manifest#CARRIER_FILTER_SMS} */
+    public static final String CARRIER_FILTER_SMS = "android.permission.CARRIER_FILTER_SMS";
+
+    /** See {@code Manifest#RECEIVE_EMERGENCY_BROADCAST} */
+    public static final String RECEIVE_EMERGENCY_BROADCAST =
+            "android.permission.RECEIVE_EMERGENCY_BROADCAST";
+
+    /** See {@code Manifest#RECEIVE_BLUETOOTH_MAP} */
+    public static final String RECEIVE_BLUETOOTH_MAP = "android.permission.RECEIVE_BLUETOOTH_MAP";
+
+    /** See {@code Manifest#MODIFY_CELL_BROADCASTS} */
+    public static final String MODIFY_CELL_BROADCASTS =
+            "android.permission.MODIFY_CELL_BROADCASTS";
+
+    /** See {@code Manifest#SET_ALARM} */
+    public static final String SET_ALARM = "com.android.alarm.permission.SET_ALARM";
+
+    /** See {@code Manifest#WRITE_VOICEMAIL} */
+    public static final String WRITE_VOICEMAIL = "com.android.voicemail.permission.WRITE_VOICEMAIL";
+
+    /** See {@code Manifest#READ_VOICEMAIL} */
+    public static final String READ_VOICEMAIL = "com.android.voicemail.permission.READ_VOICEMAIL";
+    /** See {@code Manifest#ACCESS_LOCATION_EXTRA_COMMANDS} */
+    public static final String ACCESS_LOCATION_EXTRA_COMMANDS = "android.permission"
+            + ".ACCESS_LOCATION_EXTRA_COMMANDS";
+    /** See {@code Manifest#INSTALL_LOCATION_PROVIDER} */
+    public static final String INSTALL_LOCATION_PROVIDER = "android.permission"
+            + ".INSTALL_LOCATION_PROVIDER";
+    /** See {@code Manifest#INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE} */
+    public static final String INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE = "android"
+            + ".permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE";
+    /** See {@code Manifest#BIND_TIME_ZONE_PROVIDER_SERVICE} */
+    public static final String BIND_TIME_ZONE_PROVIDER_SERVICE =
+            "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
+    /** See {@code Manifest#HDMI_CEC} */
+    public static final String HDMI_CEC = "android.permission.HDMI_CEC";
+    /** See {@code Manifest#LOCATION_HARDWARE} */
+    public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
+    /** See {@code Manifest#ACCESS_CONTEXT_HUB} */
+    public static final String ACCESS_CONTEXT_HUB = "android.permission.ACCESS_CONTEXT_HUB";
+    /** See {@code Manifest#ACCESS_MOCK_LOCATION} */
+    public static final String ACCESS_MOCK_LOCATION = "android.permission.ACCESS_MOCK_LOCATION";
+    /** See {@code Manifest#AUTOMOTIVE_GNSS_CONTROLS} */
+    public static final String AUTOMOTIVE_GNSS_CONTROLS =
+            "android.permission.AUTOMOTIVE_GNSS_CONTROLS";
+    /** See {@code Manifest#INTERNET} */
+    public static final String INTERNET = "android.permission.INTERNET";
+    /** See {@code Manifest#ACCESS_NETWORK_STATE} */
+    public static final String ACCESS_NETWORK_STATE = "android.permission.ACCESS_NETWORK_STATE";
+    /** See {@code Manifest#ACCESS_WIFI_STATE} */
+    public static final String ACCESS_WIFI_STATE = "android.permission.ACCESS_WIFI_STATE";
+    /** See {@code Manifest#CHANGE_WIFI_STATE} */
+    public static final String CHANGE_WIFI_STATE = "android.permission.CHANGE_WIFI_STATE";
+    /** See {@code Manifest#MANAGE_WIFI_AUTO_JOIN} */
+    public static final String MANAGE_WIFI_AUTO_JOIN = "android.permission.MANAGE_WIFI_AUTO_JOIN";
+    /** See {@code Manifest#MANAGE_IPSEC_TUNNELS} */
+    public static final String MANAGE_IPSEC_TUNNELS = "android.permission.MANAGE_IPSEC_TUNNELS";
+    /** See {@code Manifest#MANAGE_TEST_NETWORKS} */
+    public static final String MANAGE_TEST_NETWORKS = "android.permission.MANAGE_TEST_NETWORKS";
+    /** See {@code Manifest#READ_WIFI_CREDENTIAL} */
+    public static final String READ_WIFI_CREDENTIAL = "android.permission.READ_WIFI_CREDENTIAL";
+    /** See {@code Manifest#TETHER_PRIVILEGED} */
+    public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+    /** See {@code Manifest#RECEIVE_WIFI_CREDENTIAL_CHANGE} */
+    public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission"
+            + ".RECEIVE_WIFI_CREDENTIAL_CHANGE";
+    /** See {@code Manifest#OVERRIDE_WIFI_CONFIG} */
+    public static final String OVERRIDE_WIFI_CONFIG = "android.permission.OVERRIDE_WIFI_CONFIG";
+    /** See {@code Manifest#SCORE_NETWORKS} */
+    public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+    /** See {@code Manifest#REQUEST_NETWORK_SCORES} */
+    public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
+    /** See {@code Manifest#RESTART_WIFI_SUBSYSTEM} */
+    public static final String RESTART_WIFI_SUBSYSTEM = "android.permission"
+            + ".RESTART_WIFI_SUBSYSTEM";
+    /** See {@code Manifest#NETWORK_AIRPLANE_MODE} */
+    public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
+    /** See {@code Manifest#NETWORK_STACK} */
+    public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
+    /** See {@code Manifest#OBSERVE_NETWORK_POLICY} */
+    public static final String OBSERVE_NETWORK_POLICY = "android.permission"
+            + ".OBSERVE_NETWORK_POLICY";
+    /** See {@code Manifest#NETWORK_FACTORY} */
+    public static final String NETWORK_FACTORY = "android.permission.NETWORK_FACTORY";
+    /** See {@code Manifest#NETWORK_STATS_PROVIDER} */
+    public static final String NETWORK_STATS_PROVIDER = "android.permission.NETWORK_STATS_PROVIDER";
+    /** See {@code Manifest#NETWORK_SETTINGS} */
+    public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
+    /** See {@code Manifest#RADIO_SCAN_WITHOUT_LOCATION} */
+    public static final String RADIO_SCAN_WITHOUT_LOCATION =
+            "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
+    /** See {@code Manifest#NETWORK_SETUP_WIZARD} */
+    public static final String NETWORK_SETUP_WIZARD = "android.permission.NETWORK_SETUP_WIZARD";
+    /** See {@code Manifest#NETWORK_MANAGED_PROVISIONING} */
+    public static final String NETWORK_MANAGED_PROVISIONING = "android.permission"
+            + ".NETWORK_MANAGED_PROVISIONING";
+    /** See {@code Manifest#NETWORK_CARRIER_PROVISIONING} */
+    public static final String NETWORK_CARRIER_PROVISIONING =
+            "android.permission.NETWORK_CARRIER_PROVISIONING";
+    /** See {@code Manifest#ACCESS_LOWPAN_STATE} */
+    public static final String ACCESS_LOWPAN_STATE = "android.permission.ACCESS_LOWPAN_STATE";
+    /** See {@code Manifest#CHANGE_LOWPAN_STATE} */
+    public static final String CHANGE_LOWPAN_STATE = "android.permission.CHANGE_LOWPAN_STATE";
+    /** See {@code Manifest#READ_LOWPAN_CREDENTIAL} */
+    public static final String READ_LOWPAN_CREDENTIAL = "android.permission.READ_LOWPAN_CREDENTIAL";
+    /** See {@code Manifest#MANAGE_LOWPAN_INTERFACES} */
+    public static final String MANAGE_LOWPAN_INTERFACES = "android.permission"
+            + ".MANAGE_LOWPAN_INTERFACES";
+    /** See {@code Manifest#NETWORK_BYPASS_PRIVATE_DNS} */
+    public static final String NETWORK_BYPASS_PRIVATE_DNS =
+            "android.permission.NETWORK_BYPASS_PRIVATE_DNS";
+    /** See {@code Manifest#WIFI_SET_DEVICE_MOBILITY_STATE} */
+    public static final String WIFI_SET_DEVICE_MOBILITY_STATE =
+            "android.permission.WIFI_SET_DEVICE_MOBILITY_STATE";
+    /** See {@code Manifest#WIFI_UPDATE_USABILITY_STATS_SCORE} */
+    public static final String WIFI_UPDATE_USABILITY_STATS_SCORE = "android.permission"
+            + ".WIFI_UPDATE_USABILITY_STATS_SCORE";
+    /** See {@code Manifest#WIFI_UPDATE_COEX_UNSAFE_CHANNELS} */
+    public static final String WIFI_UPDATE_COEX_UNSAFE_CHANNELS = "android.permission"
+            + ".WIFI_UPDATE_COEX_UNSAFE_CHANNELS";
+    /** See {@code Manifest#WIFI_ACCESS_COEX_UNSAFE_CHANNELS} */
+    public static final String WIFI_ACCESS_COEX_UNSAFE_CHANNELS = "android.permission"
+            + ".WIFI_ACCESS_COEX_UNSAFE_CHANNELS";
+    /** See {@code Manifest#MANAGE_WIFI_COUNTRY_CODE} */
+    public static final String MANAGE_WIFI_COUNTRY_CODE =
+            "android.permission.MANAGE_WIFI_COUNTRY_CODE";
+    /** See {@code Manifest#CONTROL_OEM_PAID_NETWORK_PREFERENCE} */
+    public static final String CONTROL_OEM_PAID_NETWORK_PREFERENCE =
+            "android.permission.CONTROL_OEM_PAID_NETWORK_PREFERENCE";
+    /** See {@code Manifest#BLUETOOTH} */
+    public static final String BLUETOOTH = "android.permission.BLUETOOTH";
+    /** See {@code Manifest#BLUETOOTH_SCAN} */
+    public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
+    /** See {@code Manifest#BLUETOOTH_CONNECT} */
+    public static final String BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT";
+    /** See {@code Manifest#BLUETOOTH_ADVERTISE} */
+    public static final String BLUETOOTH_ADVERTISE = "android.permission.BLUETOOTH_ADVERTISE";
+    /** See {@code Manifest#UWB_RANGING} */
+    public static final String UWB_RANGING = "android.permission.UWB_RANGING";
+    /** See {@code Manifest#NEARBY_WIFI_DEVICES} */
+    public static final String NEARBY_WIFI_DEVICES = "android.permission.NEARBY_WIFI_DEVICES";
+    /** See {@code Manifest#SUSPEND_APPS} */
+    public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
+    /** See {@code Manifest#BLUETOOTH_ADMIN} */
+    public static final String BLUETOOTH_ADMIN = "android.permission.BLUETOOTH_ADMIN";
+    /** See {@code Manifest#BLUETOOTH_PRIVILEGED} */
+    public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
+    /** See {@code Manifest#BLUETOOTH_MAP} */
+    public static final String BLUETOOTH_MAP = "android.permission.BLUETOOTH_MAP";
+    /** See {@code Manifest#BLUETOOTH_STACK} */
+    public static final String BLUETOOTH_STACK = "android.permission.BLUETOOTH_STACK";
+    /** See {@code Manifest#VIRTUAL_INPUT_DEVICE} */
+    public static final String VIRTUAL_INPUT_DEVICE = "android.permission.VIRTUAL_INPUT_DEVICE";
+    /** See {@code Manifest#NFC} */
+    public static final String NFC = "android.permission.NFC";
+    /** See {@code Manifest#NFC_TRANSACTION_EVENT} */
+    public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
+    /** See {@code Manifest#NFC_PREFERRED_PAYMENT_INFO} */
+    public static final String NFC_PREFERRED_PAYMENT_INFO =
+            "android.permission.NFC_PREFERRED_PAYMENT_INFO";
+    /** See {@code Manifest#NFC_SET_CONTROLLER_ALWAYS_ON} */
+    public static final String NFC_SET_CONTROLLER_ALWAYS_ON = "android.permission"
+            + ".NFC_SET_CONTROLLER_ALWAYS_ON";
+    /** See {@code Manifest#SECURE_ELEMENT_PRIVILEGED_OPERATION} */
+    public static final String SECURE_ELEMENT_PRIVILEGED_OPERATION = "android.permission"
+            + ".SECURE_ELEMENT_PRIVILEGED_OPERATION";
+    /** See {@code Manifest#CONNECTIVITY_INTERNAL} */
+    public static final String CONNECTIVITY_INTERNAL = "android.permission.CONNECTIVITY_INTERNAL";
+    /** See {@code Manifest#CONNECTIVITY_USE_RESTRICTED_NETWORKS} */
+    public static final String CONNECTIVITY_USE_RESTRICTED_NETWORKS =
+            "android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS";
+    /** See {@code Manifest#NETWORK_SIGNAL_STRENGTH_WAKEUP} */
+    public static final String NETWORK_SIGNAL_STRENGTH_WAKEUP =
+            "android.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP";
+    /** See {@code Manifest#PACKET_KEEPALIVE_OFFLOAD} */
+    public static final String PACKET_KEEPALIVE_OFFLOAD =
+            "android.permission.PACKET_KEEPALIVE_OFFLOAD";
+    /** See {@code Manifest#RECEIVE_DATA_ACTIVITY_CHANGE} */
+    public static final String RECEIVE_DATA_ACTIVITY_CHANGE =
+            "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE";
+    /** See {@code Manifest#LOOP_RADIO} */
+    public static final String LOOP_RADIO = "android.permission.LOOP_RADIO";
+    /** See {@code Manifest#NFC_HANDOVER_STATUS} */
+    public static final String NFC_HANDOVER_STATUS = "android.permission.NFC_HANDOVER_STATUS";
+    /** See {@code Manifest#MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED} */
+    public static final String MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED =
+            "android.permission.MANAGE_BLUETOOTH_WHEN_WIRELESS_CONSENT_REQUIRED";
+    /** See {@code Manifest#ENABLE_TEST_HARNESS_MODE} */
+    public static final String ENABLE_TEST_HARNESS_MODE =
+            "android.permission.ENABLE_TEST_HARNESS_MODE";
+    /** See {@code Manifest#GET_ACCOUNTS} */
+    public static final String GET_ACCOUNTS = "android.permission.GET_ACCOUNTS";
+    /** See {@code Manifest#ACCOUNT_MANAGER} */
+    public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
+    /** See {@code Manifest#CHANGE_WIFI_MULTICAST_STATE} */
+    public static final String CHANGE_WIFI_MULTICAST_STATE = "android.permission"
+            + ".CHANGE_WIFI_MULTICAST_STATE";
+    /** See {@code Manifest#VIBRATE} */
+    public static final String VIBRATE = "android.permission.VIBRATE";
+    /** See {@code Manifest#VIBRATE_ALWAYS_ON} */
+    public static final String VIBRATE_ALWAYS_ON = "android.permission.VIBRATE_ALWAYS_ON";
+    /** See {@code Manifest#ACCESS_VIBRATOR_STATE} */
+    public static final String ACCESS_VIBRATOR_STATE = "android.permission.ACCESS_VIBRATOR_STATE";
+    /** See {@code Manifest#WAKE_LOCK} */
+    public static final String WAKE_LOCK = "android.permission.WAKE_LOCK";
+    /** See {@code Manifest#TRANSMIT_IR} */
+    public static final String TRANSMIT_IR = "android.permission.TRANSMIT_IR";
+    /** See {@code Manifest#MODIFY_AUDIO_SETTINGS} */
+    public static final String MODIFY_AUDIO_SETTINGS = "android.permission.MODIFY_AUDIO_SETTINGS";
+    /** See {@code Manifest#MANAGE_FACTORY_RESET_PROTECTION} */
+    public static final String MANAGE_FACTORY_RESET_PROTECTION = "android.permission"
+            + ".MANAGE_FACTORY_RESET_PROTECTION";
+    /** See {@code Manifest#MANAGE_USB} */
+    public static final String MANAGE_USB = "android.permission.MANAGE_USB";
+    /** See {@code Manifest#MANAGE_DEBUGGING} */
+    public static final String MANAGE_DEBUGGING = "android.permission.MANAGE_DEBUGGING";
+    /** See {@code Manifest#ACCESS_MTP} */
+    public static final String ACCESS_MTP = "android.permission.ACCESS_MTP";
+    /** See {@code Manifest#HARDWARE_TEST} */
+    public static final String HARDWARE_TEST = "android.permission.HARDWARE_TEST";
+    /** See {@code Manifest#MANAGE_DYNAMIC_SYSTEM} */
+    public static final String MANAGE_DYNAMIC_SYSTEM = "android.permission.MANAGE_DYNAMIC_SYSTEM";
+    /** See {@code Manifest#INSTALL_DYNAMIC_SYSTEM} */
+    public static final String INSTALL_DYNAMIC_SYSTEM = "android.permission"
+            + ".INSTALL_DYNAMIC_SYSTEM";
+    /** See {@code Manifest#ACCESS_BROADCAST_RADIO} */
+    public static final String ACCESS_BROADCAST_RADIO = "android.permission"
+            + ".ACCESS_BROADCAST_RADIO";
+    /** See {@code Manifest#ACCESS_FM_RADIO} */
+    public static final String ACCESS_FM_RADIO = "android.permission.ACCESS_FM_RADIO";
+    /** See {@code Manifest#NET_ADMIN} */
+    public static final String NET_ADMIN = "android.permission.NET_ADMIN";
+    /** See {@code Manifest#REMOTE_AUDIO_PLAYBACK} */
+    public static final String REMOTE_AUDIO_PLAYBACK = "android.permission.REMOTE_AUDIO_PLAYBACK";
+    /** See {@code Manifest#TV_INPUT_HARDWARE} */
+    public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
+    /** See {@code Manifest#CAPTURE_TV_INPUT} */
+    public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT";
+    /** See {@code Manifest#DVB_DEVICE} */
+    public static final String DVB_DEVICE = "android.permission.DVB_DEVICE";
+    /** See {@code Manifest#MANAGE_CARRIER_OEM_UNLOCK_STATE} */
+    public static final String MANAGE_CARRIER_OEM_UNLOCK_STATE = "android.permission"
+            + ".MANAGE_CARRIER_OEM_UNLOCK_STATE";
+    /** See {@code Manifest#MANAGE_USER_OEM_UNLOCK_STATE} */
+    public static final String MANAGE_USER_OEM_UNLOCK_STATE = "android.permission"
+            + ".MANAGE_USER_OEM_UNLOCK_STATE";
+    /** See {@code Manifest#READ_OEM_UNLOCK_STATE} */
+    public static final String READ_OEM_UNLOCK_STATE = "android.permission.READ_OEM_UNLOCK_STATE";
+    /** See {@code Manifest#OEM_UNLOCK_STATE} */
+    public static final String OEM_UNLOCK_STATE = "android.permission.OEM_UNLOCK_STATE";
+    /** See {@code Manifest#ACCESS_PDB_STATE} */
+    public static final String ACCESS_PDB_STATE = "android.permission.ACCESS_PDB_STATE";
+    /** See {@code Manifest#TEST_BLACKLISTED_PASSWORD} */
+    public static final String TEST_BLACKLISTED_PASSWORD =
+            "android.permission.TEST_BLACKLISTED_PASSWORD";
+    /** See {@code Manifest#NOTIFY_PENDING_SYSTEM_UPDATE} */
+    public static final String NOTIFY_PENDING_SYSTEM_UPDATE =
+            "android.permission.NOTIFY_PENDING_SYSTEM_UPDATE";
+    /** See {@code Manifest#CAMERA_DISABLE_TRANSMIT_LED} */
+    public static final String CAMERA_DISABLE_TRANSMIT_LED =
+            "android.permission.CAMERA_DISABLE_TRANSMIT_LED";
+    /** See {@code Manifest#CAMERA_SEND_SYSTEM_EVENTS} */
+    public static final String CAMERA_SEND_SYSTEM_EVENTS =
+            "android.permission.CAMERA_SEND_SYSTEM_EVENTS";
+    /** See {@code Manifest#CAMERA_INJECT_EXTERNAL_CAMERA} */
+    public static final String CAMERA_INJECT_EXTERNAL_CAMERA =
+            "android.permission.CAMERA_INJECT_EXTERNAL_CAMERA";
+    /** See {@code Manifest#GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS} */
+    public static final String GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS = "android"
+            + ".permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS";
+    /** See {@code Manifest#MODIFY_PHONE_STATE} */
+    public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+    /** See {@code Manifest#READ_PRECISE_PHONE_STATE} */
+    public static final String READ_PRECISE_PHONE_STATE =
+            "android.permission.READ_PRECISE_PHONE_STATE";
+    /** See {@code Manifest#READ_PRIVILEGED_PHONE_STATE} */
+    public static final String READ_PRIVILEGED_PHONE_STATE = "android.permission"
+            + ".READ_PRIVILEGED_PHONE_STATE";
+    /** See {@code Manifest#USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER} */
+    public static final String USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER =
+            "android.permission.USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER";
+    /** See {@code Manifest#READ_ACTIVE_EMERGENCY_SESSION} */
+    public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission"
+            + ".READ_ACTIVE_EMERGENCY_SESSION";
+    /** See {@code Manifest#LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH} */
+    public static final String LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH = "android.permission"
+            + ".LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH";
+    /** See {@code Manifest#REGISTER_SIM_SUBSCRIPTION} */
+    public static final String REGISTER_SIM_SUBSCRIPTION =
+            "android.permission.REGISTER_SIM_SUBSCRIPTION";
+    /** See {@code Manifest#REGISTER_CALL_PROVIDER} */
+    public static final String REGISTER_CALL_PROVIDER = "android.permission"
+            + ".REGISTER_CALL_PROVIDER";
+    /** See {@code Manifest#REGISTER_CONNECTION_MANAGER} */
+    public static final String REGISTER_CONNECTION_MANAGER =
+            "android.permission.REGISTER_CONNECTION_MANAGER";
+    /** See {@code Manifest#BIND_INCALL_SERVICE} */
+    public static final String BIND_INCALL_SERVICE = "android.permission.BIND_INCALL_SERVICE";
+    /** See {@code Manifest#MANAGE_ONGOING_CALLS} */
+    public static final String MANAGE_ONGOING_CALLS = "android.permission.MANAGE_ONGOING_CALLS";
+    /** See {@code Manifest#NETWORK_SCAN} */
+    public static final String NETWORK_SCAN = "android.permission.NETWORK_SCAN";
+    /** See {@code Manifest#BIND_VISUAL_VOICEMAIL_SERVICE} */
+    public static final String BIND_VISUAL_VOICEMAIL_SERVICE = "android.permission"
+            + ".BIND_VISUAL_VOICEMAIL_SERVICE";
+    /** See {@code Manifest#BIND_SCREENING_SERVICE} */
+    public static final String BIND_SCREENING_SERVICE = "android.permission.BIND_SCREENING_SERVICE";
+    /** See {@code Manifest#BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE} */
+    public static final String BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE =
+            "android.permission.BIND_PHONE_ACCOUNT_SUGGESTION_SERVICE";
+    /** See {@code Manifest#BIND_CALL_DIAGNOSTIC_SERVICE} */
+    public static final String BIND_CALL_DIAGNOSTIC_SERVICE = "android.permission"
+            + ".BIND_CALL_DIAGNOSTIC_SERVICE";
+    /** See {@code Manifest#BIND_CALL_REDIRECTION_SERVICE} */
+    public static final String BIND_CALL_REDIRECTION_SERVICE =
+            "android.permission.BIND_CALL_REDIRECTION_SERVICE";
+    /** See {@code Manifest#BIND_CONNECTION_SERVICE} */
+    public static final String BIND_CONNECTION_SERVICE =
+            "android.permission.BIND_CONNECTION_SERVICE";
+    /** See {@code Manifest#BIND_TELECOM_CONNECTION_SERVICE} */
+    public static final String BIND_TELECOM_CONNECTION_SERVICE = "android.permission"
+            + ".BIND_TELECOM_CONNECTION_SERVICE";
+    /** See {@code Manifest#CONTROL_INCALL_EXPERIENCE} */
+    public static final String CONTROL_INCALL_EXPERIENCE = "android.permission"
+            + ".CONTROL_INCALL_EXPERIENCE";
+    /** See {@code Manifest#RECEIVE_STK_COMMANDS} */
+    public static final String RECEIVE_STK_COMMANDS = "android.permission.RECEIVE_STK_COMMANDS";
+    /** See {@code Manifest#SEND_EMBMS_INTENTS} */
+    public static final String SEND_EMBMS_INTENTS = "android.permission.SEND_EMBMS_INTENTS";
+    /** See {@code Manifest#MANAGE_SENSORS} */
+    public static final String MANAGE_SENSORS = "android.permission.MANAGE_SENSORS";
+    /** See {@code Manifest#BIND_IMS_SERVICE} */
+    public static final String BIND_IMS_SERVICE = "android.permission.BIND_IMS_SERVICE";
+    /** See {@code Manifest#BIND_TELEPHONY_DATA_SERVICE} */
+    public static final String BIND_TELEPHONY_DATA_SERVICE =
+            "android.permission.BIND_TELEPHONY_DATA_SERVICE";
+    /** See {@code Manifest#BIND_TELEPHONY_NETWORK_SERVICE} */
+    public static final String BIND_TELEPHONY_NETWORK_SERVICE =
+            "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
+    /** See {@code Manifest#WRITE_EMBEDDED_SUBSCRIPTIONS} */
+    public static final String WRITE_EMBEDDED_SUBSCRIPTIONS = "android.permission"
+            + ".WRITE_EMBEDDED_SUBSCRIPTIONS";
+    /** See {@code Manifest#BIND_EUICC_SERVICE} */
+    public static final String BIND_EUICC_SERVICE = "android.permission.BIND_EUICC_SERVICE";
+    /** See {@code Manifest#READ_CARRIER_APP_INFO} */
+    public static final String READ_CARRIER_APP_INFO = "android.permission.READ_CARRIER_APP_INFO";
+    /** See {@code Manifest#BIND_GBA_SERVICE} */
+    public static final String BIND_GBA_SERVICE = "android.permission.BIND_GBA_SERVICE";
+    /** See {@code Manifest#ACCESS_RCS_USER_CAPABILITY_EXCHANGE} */
+    public static final String ACCESS_RCS_USER_CAPABILITY_EXCHANGE =
+            "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE";
+    /** See {@code Manifest#WRITE_MEDIA_STORAGE} */
+    public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
+    /** See {@code Manifest#MANAGE_DOCUMENTS} */
+    public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
+    /** See {@code Manifest#CACHE_CONTENT} */
+    public static final String CACHE_CONTENT = "android.permission.CACHE_CONTENT";
+    /** See {@code Manifest#ALLOCATE_AGGRESSIVE} */
+    public static final String ALLOCATE_AGGRESSIVE = "android.permission.ALLOCATE_AGGRESSIVE";
+    /** See {@code Manifest#USE_RESERVED_DISK} */
+    public static final String USE_RESERVED_DISK = "android.permission.USE_RESERVED_DISK";
+    /** See {@code Manifest#DISABLE_KEYGUARD} */
+    public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
+    /** See {@code Manifest#REQUEST_PASSWORD_COMPLEXITY} */
+    public static final String REQUEST_PASSWORD_COMPLEXITY =
+            "android.permission.REQUEST_PASSWORD_COMPLEXITY";
+    /** See {@code Manifest#GET_TASKS} */
+    public static final String GET_TASKS = "android.permission.GET_TASKS";
+    /** See {@code Manifest#REAL_GET_TASKS} */
+    public static final String REAL_GET_TASKS = "android.permission.REAL_GET_TASKS";
+    /** See {@code Manifest#START_TASKS_FROM_RECENTS} */
+    public static final String START_TASKS_FROM_RECENTS =
+            "android.permission.START_TASKS_FROM_RECENTS";
+    /** See {@code Manifest#INTERACT_ACROSS_USERS} */
+    public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
+    /** See {@code Manifest#INTERACT_ACROSS_USERS_FULL} */
+    public static final String INTERACT_ACROSS_USERS_FULL =
+            "android.permission.INTERACT_ACROSS_USERS_FULL";
+    /** See {@code Manifest#INTERACT_ACROSS_PROFILES} */
+    public static final String INTERACT_ACROSS_PROFILES = "android.permission"
+            + ".INTERACT_ACROSS_PROFILES";
+    /** See {@code Manifest#CONFIGURE_INTERACT_ACROSS_PROFILES} */
+    public static final String CONFIGURE_INTERACT_ACROSS_PROFILES =
+            "android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES";
+    /** See {@code Manifest#MANAGE_USERS} */
+    public static final String MANAGE_USERS = "android.permission.MANAGE_USERS";
+    /** See {@code Manifest#CREATE_USERS} */
+    public static final String CREATE_USERS = "android.permission.CREATE_USERS";
+    /** See {@code Manifest#QUERY_USERS} */
+    public static final String QUERY_USERS = "android.permission.QUERY_USERS";
+    /** See {@code Manifest#ACCESS_BLOBS_ACROSS_USERS} */
+    public static final String ACCESS_BLOBS_ACROSS_USERS = "android.permission"
+            + ".ACCESS_BLOBS_ACROSS_USERS";
+    /** See {@code Manifest#MANAGE_PROFILE_AND_DEVICE_OWNERS} */
+    public static final String MANAGE_PROFILE_AND_DEVICE_OWNERS = "android.permission"
+            + ".MANAGE_PROFILE_AND_DEVICE_OWNERS";
+    /** See {@code Manifest#QUERY_ADMIN_POLICY} */
+    public static final String QUERY_ADMIN_POLICY = "android.permission.QUERY_ADMIN_POLICY";
+    /** See {@code Manifest#CLEAR_FREEZE_PERIOD} */
+    public static final String CLEAR_FREEZE_PERIOD = "android.permission.CLEAR_FREEZE_PERIOD";
+    /** See {@code Manifest#FORCE_DEVICE_POLICY_MANAGER_LOGS} */
+    public static final String FORCE_DEVICE_POLICY_MANAGER_LOGS = "android.permission"
+            + ".FORCE_DEVICE_POLICY_MANAGER_LOGS";
+    /** See {@code Manifest#GET_DETAILED_TASKS} */
+    public static final String GET_DETAILED_TASKS = "android.permission.GET_DETAILED_TASKS";
+    /** See {@code Manifest#REORDER_TASKS} */
+    public static final String REORDER_TASKS = "android.permission.REORDER_TASKS";
+    /** See {@code Manifest#REMOVE_TASKS} */
+    public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
+    /** See {@code Manifest#MANAGE_ACTIVITY_STACKS} */
+    public static final String MANAGE_ACTIVITY_STACKS = "android.permission.MANAGE_ACTIVITY_STACKS";
+    /** See {@code Manifest#MANAGE_ACTIVITY_TASKS} */
+    public static final String MANAGE_ACTIVITY_TASKS = "android.permission.MANAGE_ACTIVITY_TASKS";
+    /** See {@code Manifest#ACTIVITY_EMBEDDING} */
+    public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
+    /** See {@code Manifest#START_ANY_ACTIVITY} */
+    public static final String START_ANY_ACTIVITY = "android.permission.START_ANY_ACTIVITY";
+    /** See {@code Manifest#START_ACTIVITIES_FROM_BACKGROUND} */
+    public static final String START_ACTIVITIES_FROM_BACKGROUND = "android.permission"
+            + ".START_ACTIVITIES_FROM_BACKGROUND";
+    /** See {@code Manifest#START_FOREGROUND_SERVICES_FROM_BACKGROUND} */
+    public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission"
+            + ".START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+    /** See {@code Manifest#SEND_SHOW_SUSPENDED_APP_DETAILS} */
+    public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission"
+            + ".SEND_SHOW_SUSPENDED_APP_DETAILS";
+    /** See {@code Manifest#START_ACTIVITY_AS_CALLER} */
+    public static final String START_ACTIVITY_AS_CALLER = "android.permission"
+            + ".START_ACTIVITY_AS_CALLER";
+    /** See {@code Manifest#RESTART_PACKAGES} */
+    public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    /** See {@code Manifest#GET_PROCESS_STATE_AND_OOM_SCORE} */
+    public static final String GET_PROCESS_STATE_AND_OOM_SCORE =
+            "android.permission.GET_PROCESS_STATE_AND_OOM_SCORE";
+    /** See {@code Manifest#GET_INTENT_SENDER_INTENT} */
+    public static final String GET_INTENT_SENDER_INTENT =
+            "android.permission.GET_INTENT_SENDER_INTENT";
+    /** See {@code Manifest#SYSTEM_ALERT_WINDOW} */
+    public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
+    /** See {@code Manifest#SYSTEM_APPLICATION_OVERLAY} */
+    public static final String SYSTEM_APPLICATION_OVERLAY =
+            "android.permission.SYSTEM_APPLICATION_OVERLAY";
+    /** See {@code Manifest#RUN_IN_BACKGROUND} */
+    public static final String RUN_IN_BACKGROUND = "android.permission.RUN_IN_BACKGROUND";
+    /** See {@code Manifest#USE_DATA_IN_BACKGROUND} */
+    public static final String USE_DATA_IN_BACKGROUND = "android.permission"
+            + ".USE_DATA_IN_BACKGROUND";
+    /** See {@code Manifest#SET_DISPLAY_OFFSET} */
+    public static final String SET_DISPLAY_OFFSET = "android.permission.SET_DISPLAY_OFFSET";
+    /** See {@code Manifest#REQUEST_COMPANION_RUN_IN_BACKGROUND} */
+    public static final String REQUEST_COMPANION_RUN_IN_BACKGROUND = "android.permission"
+            + ".REQUEST_COMPANION_RUN_IN_BACKGROUND";
+    /** See {@code Manifest#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND} */
+    public static final String REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND =
+            "android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+    /** See {@code Manifest#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} */
+    public static final String REQUEST_COMPANION_USE_DATA_IN_BACKGROUND = "android.permission"
+            + ".REQUEST_COMPANION_USE_DATA_IN_BACKGROUND";
+    /** See {@code Manifest#REQUEST_COMPANION_PROFILE_WATCH} */
+    public static final String REQUEST_COMPANION_PROFILE_WATCH =
+            "android.permission.REQUEST_COMPANION_PROFILE_WATCH";
+    /** See {@code Manifest#REQUEST_COMPANION_PROFILE_APP_STREAMING} */
+    public static final String REQUEST_COMPANION_PROFILE_APP_STREAMING = "android.permission"
+            + ".REQUEST_COMPANION_PROFILE_APP_STREAMING";
+    /** See {@code Manifest#REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION} */
+    public static final String REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION =
+            "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION";
+    /** See {@code Manifest#REQUEST_COMPANION_SELF_MANAGED} */
+    public static final String REQUEST_COMPANION_SELF_MANAGED = "android.permission"
+            + ".REQUEST_COMPANION_SELF_MANAGED";
+    /** See {@code Manifest#COMPANION_APPROVE_WIFI_CONNECTIONS} */
+    public static final String COMPANION_APPROVE_WIFI_CONNECTIONS = "android.permission"
+            + ".COMPANION_APPROVE_WIFI_CONNECTIONS";
+    /** See {@code Manifest#READ_PROJECTION_STATE} */
+    public static final String READ_PROJECTION_STATE = "android.permission.READ_PROJECTION_STATE";
+    /** See {@code Manifest#TOGGLE_AUTOMOTIVE_PROJECTION} */
+    public static final String TOGGLE_AUTOMOTIVE_PROJECTION =
+            "android.permission.TOGGLE_AUTOMOTIVE_PROJECTION";
+    /** See {@code Manifest#HIDE_OVERLAY_WINDOWS} */
+    public static final String HIDE_OVERLAY_WINDOWS = "android.permission.HIDE_OVERLAY_WINDOWS";
+    /** See {@code Manifest#SET_WALLPAPER} */
+    public static final String SET_WALLPAPER = "android.permission.SET_WALLPAPER";
+    /** See {@code Manifest#SET_WALLPAPER_HINTS} */
+    public static final String SET_WALLPAPER_HINTS = "android.permission.SET_WALLPAPER_HINTS";
+    /** See {@code Manifest#READ_WALLPAPER_INTERNAL} */
+    public static final String READ_WALLPAPER_INTERNAL = "android.permission"
+            + ".READ_WALLPAPER_INTERNAL";
+    /** See {@code Manifest#SET_TIME} */
+    public static final String SET_TIME = "android.permission.SET_TIME";
+    /** See {@code Manifest#SET_TIME_ZONE} */
+    public static final String SET_TIME_ZONE = "android.permission.SET_TIME_ZONE";
+    /** See {@code Manifest#SUGGEST_TELEPHONY_TIME_AND_ZONE} */
+    public static final String SUGGEST_TELEPHONY_TIME_AND_ZONE =
+            "android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE";
+    /** See {@code Manifest#SUGGEST_MANUAL_TIME_AND_ZONE} */
+    public static final String SUGGEST_MANUAL_TIME_AND_ZONE = "android.permission"
+            + ".SUGGEST_MANUAL_TIME_AND_ZONE";
+    /** See {@code Manifest#SUGGEST_EXTERNAL_TIME} */
+    public static final String SUGGEST_EXTERNAL_TIME = "android.permission.SUGGEST_EXTERNAL_TIME";
+    /** See {@code Manifest#MANAGE_TIME_AND_ZONE_DETECTION} */
+    public static final String MANAGE_TIME_AND_ZONE_DETECTION = "android.permission"
+            + ".MANAGE_TIME_AND_ZONE_DETECTION";
+    /** See {@code Manifest#EXPAND_STATUS_BAR} */
+    public static final String EXPAND_STATUS_BAR = "android.permission.EXPAND_STATUS_BAR";
+    /** See {@code Manifest#INSTALL_SHORTCUT} */
+    public static final String INSTALL_SHORTCUT = "com.android.launcher.permission"
+            + ".INSTALL_SHORTCUT";
+    /** See {@code Manifest#READ_SYNC_SETTINGS} */
+    public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
+    /** See {@code Manifest#WRITE_SYNC_SETTINGS} */
+    public static final String WRITE_SYNC_SETTINGS = "android.permission.WRITE_SYNC_SETTINGS";
+    /** See {@code Manifest#READ_SYNC_STATS} */
+    public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
+    /** See {@code Manifest#SET_SCREEN_COMPATIBILITY} */
+    public static final String SET_SCREEN_COMPATIBILITY = "android.permission"
+            + ".SET_SCREEN_COMPATIBILITY";
+    /** See {@code Manifest#CHANGE_CONFIGURATION} */
+    public static final String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
+    /** See {@code Manifest#WRITE_GSERVICES} */
+    public static final String WRITE_GSERVICES = "android.permission.WRITE_GSERVICES";
+    /** See {@code Manifest#WRITE_DEVICE_CONFIG} */
+    public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
+    /** See {@code Manifest#READ_DEVICE_CONFIG} */
+    public static final String READ_DEVICE_CONFIG = "android.permission.READ_DEVICE_CONFIG";
+    /** See {@code Manifest#READ_APP_SPECIFIC_LOCALES} */
+    public static final String READ_APP_SPECIFIC_LOCALES =
+            "android.permission.READ_APP_SPECIFIC_LOCALES";
+    /** See {@code Manifest#MONITOR_DEVICE_CONFIG_ACCESS} */
+    public static final String MONITOR_DEVICE_CONFIG_ACCESS =
+            "android.permission.MONITOR_DEVICE_CONFIG_ACCESS";
+    /** See {@code Manifest#FORCE_STOP_PACKAGES} */
+    public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
+    /** See {@code Manifest#RETRIEVE_WINDOW_CONTENT} */
+    public static final String RETRIEVE_WINDOW_CONTENT =
+            "android.permission.RETRIEVE_WINDOW_CONTENT";
+    /** See {@code Manifest#SET_ANIMATION_SCALE} */
+    public static final String SET_ANIMATION_SCALE = "android.permission.SET_ANIMATION_SCALE";
+    /** See {@code Manifest#PERSISTENT_ACTIVITY} */
+    public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
+    /** See {@code Manifest#GET_PACKAGE_SIZE} */
+    public static final String GET_PACKAGE_SIZE = "android.permission.GET_PACKAGE_SIZE";
+    /** See {@code Manifest#RECEIVE_BOOT_COMPLETED} */
+    public static final String RECEIVE_BOOT_COMPLETED = "android.permission.RECEIVE_BOOT_COMPLETED";
+    /** See {@code Manifest#BROADCAST_STICKY} */
+    public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
+    /** See {@code Manifest#MOUNT_UNMOUNT_FILESYSTEMS} */
+    public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission"
+            + ".MOUNT_UNMOUNT_FILESYSTEMS";
+    /** See {@code Manifest#MOUNT_FORMAT_FILESYSTEMS} */
+    public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission"
+            + ".MOUNT_FORMAT_FILESYSTEMS";
+    /** See {@code Manifest#STORAGE_INTERNAL} */
+    public static final String STORAGE_INTERNAL = "android.permission.STORAGE_INTERNAL";
+    /** See {@code Manifest#ASEC_ACCESS} */
+    public static final String ASEC_ACCESS = "android.permission.ASEC_ACCESS";
+    /** See {@code Manifest#ASEC_CREATE} */
+    public static final String ASEC_CREATE = "android.permission.ASEC_CREATE";
+    /** See {@code Manifest#ASEC_DESTROY} */
+    public static final String ASEC_DESTROY = "android.permission.ASEC_DESTROY";
+    /** See {@code Manifest#ASEC_MOUNT_UNMOUNT} */
+    public static final String ASEC_MOUNT_UNMOUNT = "android.permission.ASEC_MOUNT_UNMOUNT";
+    /** See {@code Manifest#ASEC_RENAME} */
+    public static final String ASEC_RENAME = "android.permission.ASEC_RENAME";
+    /** See {@code Manifest#WRITE_APN_SETTINGS} */
+    public static final String WRITE_APN_SETTINGS = "android.permission.WRITE_APN_SETTINGS";
+    /** See {@code Manifest#CHANGE_NETWORK_STATE} */
+    public static final String CHANGE_NETWORK_STATE = "android.permission.CHANGE_NETWORK_STATE";
+    /** See {@code Manifest#CLEAR_APP_CACHE} */
+    public static final String CLEAR_APP_CACHE = "android.permission.CLEAR_APP_CACHE";
+    /** See {@code Manifest#ALLOW_ANY_CODEC_FOR_PLAYBACK} */
+    public static final String ALLOW_ANY_CODEC_FOR_PLAYBACK = "android.permission"
+            + ".ALLOW_ANY_CODEC_FOR_PLAYBACK";
+    /** See {@code Manifest#MANAGE_CA_CERTIFICATES} */
+    public static final String MANAGE_CA_CERTIFICATES = "android.permission"
+            + ".MANAGE_CA_CERTIFICATES";
+    /** See {@code Manifest#RECOVERY} */
+    public static final String RECOVERY = "android.permission.RECOVERY";
+    /** See {@code Manifest#BIND_RESUME_ON_REBOOT_SERVICE} */
+    public static final String BIND_RESUME_ON_REBOOT_SERVICE = "android.permission"
+            + ".BIND_RESUME_ON_REBOOT_SERVICE";
+    /** See {@code Manifest#READ_SYSTEM_UPDATE_INFO} */
+    public static final String READ_SYSTEM_UPDATE_INFO = "android.permission"
+            + ".READ_SYSTEM_UPDATE_INFO";
+    /** See {@code Manifest#BIND_JOB_SERVICE} */
+    public static final String BIND_JOB_SERVICE = "android.permission.BIND_JOB_SERVICE";
+    /** See {@code Manifest#UPDATE_CONFIG} */
+    public static final String UPDATE_CONFIG = "android.permission.UPDATE_CONFIG";
+    /** See {@code Manifest#QUERY_TIME_ZONE_RULES} */
+    public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+    /** See {@code Manifest#UPDATE_TIME_ZONE_RULES} */
+    public static final String UPDATE_TIME_ZONE_RULES = "android.permission"
+            + ".UPDATE_TIME_ZONE_RULES";
+    /** See {@code Manifest#TRIGGER_TIME_ZONE_RULES_CHECK} */
+    public static final String TRIGGER_TIME_ZONE_RULES_CHECK = "android.permission"
+            + ".TRIGGER_TIME_ZONE_RULES_CHECK";
+    /** See {@code Manifest#RESET_SHORTCUT_MANAGER_THROTTLING} */
+    public static final String RESET_SHORTCUT_MANAGER_THROTTLING =
+            "android.permission.RESET_SHORTCUT_MANAGER_THROTTLING";
+    /** See {@code Manifest#BIND_NETWORK_RECOMMENDATION_SERVICE} */
+    public static final String BIND_NETWORK_RECOMMENDATION_SERVICE =
+            "android.permission.BIND_NETWORK_RECOMMENDATION_SERVICE";
+    /** See {@code Manifest#MANAGE_CREDENTIAL_MANAGEMENT_APP} */
+    public static final String MANAGE_CREDENTIAL_MANAGEMENT_APP = "android.permission"
+            + ".MANAGE_CREDENTIAL_MANAGEMENT_APP";
+    /** See {@code Manifest#UPDATE_FONTS} */
+    public static final String UPDATE_FONTS = "android.permission.UPDATE_FONTS";
+    /** See {@code Manifest#USE_ATTESTATION_VERIFICATION_SERVICE} */
+    public static final String USE_ATTESTATION_VERIFICATION_SERVICE =
+            "android.permission.USE_ATTESTATION_VERIFICATION_SERVICE";
+    /** See {@code Manifest#VERIFY_ATTESTATION} */
+    public static final String VERIFY_ATTESTATION = "android.permission.VERIFY_ATTESTATION";
+    /** See {@code Manifest#BIND_ATTESTATION_VERIFICATION_SERVICE} */
+    public static final String BIND_ATTESTATION_VERIFICATION_SERVICE = "android.permission"
+            + ".BIND_ATTESTATION_VERIFICATION_SERVICE";
+    /** See {@code Manifest#WRITE_SECURE_SETTINGS} */
+    public static final String WRITE_SECURE_SETTINGS = "android.permission.WRITE_SECURE_SETTINGS";
+    /** See {@code Manifest#DUMP} */
+    public static final String DUMP = "android.permission.DUMP";
+    /** See {@code Manifest#CONTROL_UI_TRACING} */
+    public static final String CONTROL_UI_TRACING = "android.permission.CONTROL_UI_TRACING";
+    /** See {@code Manifest#READ_LOGS} */
+    public static final String READ_LOGS = "android.permission.READ_LOGS";
+    /** See {@code Manifest#SET_DEBUG_APP} */
+    public static final String SET_DEBUG_APP = "android.permission.SET_DEBUG_APP";
+    /** See {@code Manifest#SET_PROCESS_LIMIT} */
+    public static final String SET_PROCESS_LIMIT = "android.permission.SET_PROCESS_LIMIT";
+    /** See {@code Manifest#SET_ALWAYS_FINISH} */
+    public static final String SET_ALWAYS_FINISH = "android.permission.SET_ALWAYS_FINISH";
+    /** See {@code Manifest#SIGNAL_PERSISTENT_PROCESSES} */
+    public static final String SIGNAL_PERSISTENT_PROCESSES =
+            "android.permission.SIGNAL_PERSISTENT_PROCESSES";
+    /** See {@code Manifest#APPROVE_INCIDENT_REPORTS} */
+    public static final String APPROVE_INCIDENT_REPORTS =
+            "android.permission.APPROVE_INCIDENT_REPORTS";
+    /** See {@code Manifest#REQUEST_INCIDENT_REPORT_APPROVAL} */
+    public static final String REQUEST_INCIDENT_REPORT_APPROVAL = "android.permission"
+            + ".REQUEST_INCIDENT_REPORT_APPROVAL";
+    /** See {@code Manifest#GET_ACCOUNTS_PRIVILEGED} */
+    public static final String GET_ACCOUNTS_PRIVILEGED = "android.permission"
+            + ".GET_ACCOUNTS_PRIVILEGED";
+    /** See {@code Manifest#GET_PASSWORD} */
+    public static final String GET_PASSWORD = "android.permission.GET_PASSWORD";
+    /** See {@code Manifest#DIAGNOSTIC} */
+    public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
+    /** See {@code Manifest#STATUS_BAR} */
+    public static final String STATUS_BAR = "android.permission.STATUS_BAR";
+    /** See {@code Manifest#TRIGGER_SHELL_BUGREPORT} */
+    public static final String TRIGGER_SHELL_BUGREPORT = "android.permission"
+            + ".TRIGGER_SHELL_BUGREPORT";
+    /** See {@code Manifest#TRIGGER_SHELL_PROFCOLLECT_UPLOAD} */
+    public static final String TRIGGER_SHELL_PROFCOLLECT_UPLOAD = "android.permission"
+            + ".TRIGGER_SHELL_PROFCOLLECT_UPLOAD";
+    /** See {@code Manifest#STATUS_BAR_SERVICE} */
+    public static final String STATUS_BAR_SERVICE = "android.permission.STATUS_BAR_SERVICE";
+    /** See {@code Manifest#BIND_QUICK_SETTINGS_TILE} */
+    public static final String BIND_QUICK_SETTINGS_TILE =
+            "android.permission.BIND_QUICK_SETTINGS_TILE";
+    /** See {@code Manifest#BIND_CONTROLS} */
+    public static final String BIND_CONTROLS = "android.permission.BIND_CONTROLS";
+    /** See {@code Manifest#FORCE_BACK} */
+    public static final String FORCE_BACK = "android.permission.FORCE_BACK";
+    /** See {@code Manifest#UPDATE_DEVICE_STATS} */
+    public static final String UPDATE_DEVICE_STATS = "android.permission.UPDATE_DEVICE_STATS";
+    /** See {@code Manifest#GET_APP_OPS_STATS} */
+    public static final String GET_APP_OPS_STATS = "android.permission.GET_APP_OPS_STATS";
+    /** See {@code Manifest#GET_HISTORICAL_APP_OPS_STATS} */
+    public static final String GET_HISTORICAL_APP_OPS_STATS = "android.permission"
+            + ".GET_HISTORICAL_APP_OPS_STATS";
+    /** See {@code Manifest#UPDATE_APP_OPS_STATS} */
+    public static final String UPDATE_APP_OPS_STATS = "android.permission.UPDATE_APP_OPS_STATS";
+    /** See {@code Manifest#MANAGE_APP_OPS_RESTRICTIONS} */
+    public static final String MANAGE_APP_OPS_RESTRICTIONS = "android.permission"
+            + ".MANAGE_APP_OPS_RESTRICTIONS";
+    /** See {@code Manifest#MANAGE_APP_OPS_MODES} */
+    public static final String MANAGE_APP_OPS_MODES = "android.permission.MANAGE_APP_OPS_MODES";
+    /** See {@code Manifest#INTERNAL_SYSTEM_WINDOW} */
+    public static final String INTERNAL_SYSTEM_WINDOW = "android.permission"
+            + ".INTERNAL_SYSTEM_WINDOW";
+    /** See {@code Manifest#UNLIMITED_TOASTS} */
+    public static final String UNLIMITED_TOASTS = "android.permission.UNLIMITED_TOASTS";
+    /** See {@code Manifest#HIDE_NON_SYSTEM_OVERLAY_WINDOWS} */
+    public static final String HIDE_NON_SYSTEM_OVERLAY_WINDOWS =
+            "android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS";
+    /** See {@code Manifest#MANAGE_APP_TOKENS} */
+    public static final String MANAGE_APP_TOKENS = "android.permission.MANAGE_APP_TOKENS";
+    /** See {@code Manifest#REGISTER_WINDOW_MANAGER_LISTENERS} */
+    public static final String REGISTER_WINDOW_MANAGER_LISTENERS = "android.permission"
+            + ".REGISTER_WINDOW_MANAGER_LISTENERS";
+    /** See {@code Manifest#FREEZE_SCREEN} */
+    public static final String FREEZE_SCREEN = "android.permission.FREEZE_SCREEN";
+    /** See {@code Manifest#INJECT_EVENTS} */
+    public static final String INJECT_EVENTS = "android.permission.INJECT_EVENTS";
+    /** See {@code Manifest#FILTER_EVENTS} */
+    public static final String FILTER_EVENTS = "android.permission.FILTER_EVENTS";
+    /** See {@code Manifest#RETRIEVE_WINDOW_TOKEN} */
+    public static final String RETRIEVE_WINDOW_TOKEN = "android.permission.RETRIEVE_WINDOW_TOKEN";
+    /** See {@code Manifest#MODIFY_ACCESSIBILITY_DATA} */
+    public static final String MODIFY_ACCESSIBILITY_DATA =
+            "android.permission.MODIFY_ACCESSIBILITY_DATA";
+    /** See {@code Manifest#ACT_AS_PACKAGE_FOR_ACCESSIBILITY} */
+    public static final String ACT_AS_PACKAGE_FOR_ACCESSIBILITY = "android.permission"
+            + ".ACT_AS_PACKAGE_FOR_ACCESSIBILITY";
+    /** See {@code Manifest#CHANGE_ACCESSIBILITY_VOLUME} */
+    public static final String CHANGE_ACCESSIBILITY_VOLUME =
+            "android.permission.CHANGE_ACCESSIBILITY_VOLUME";
+    /** See {@code Manifest#FRAME_STATS} */
+    public static final String FRAME_STATS = "android.permission.FRAME_STATS";
+    /** See {@code Manifest#TEMPORARY_ENABLE_ACCESSIBILITY} */
+    public static final String TEMPORARY_ENABLE_ACCESSIBILITY = "android.permission"
+            + ".TEMPORARY_ENABLE_ACCESSIBILITY";
+    /** See {@code Manifest#OPEN_ACCESSIBILITY_DETAILS_SETTINGS} */
+    public static final String OPEN_ACCESSIBILITY_DETAILS_SETTINGS = "android.permission"
+            + ".OPEN_ACCESSIBILITY_DETAILS_SETTINGS";
+    /** See {@code Manifest#SET_ACTIVITY_WATCHER} */
+    public static final String SET_ACTIVITY_WATCHER = "android.permission.SET_ACTIVITY_WATCHER";
+    /** See {@code Manifest#SHUTDOWN} */
+    public static final String SHUTDOWN = "android.permission.SHUTDOWN";
+    /** See {@code Manifest#STOP_APP_SWITCHES} */
+    public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
+    /** See {@code Manifest#GET_TOP_ACTIVITY_INFO} */
+    public static final String GET_TOP_ACTIVITY_INFO = "android.permission.GET_TOP_ACTIVITY_INFO";
+    /** See {@code Manifest#READ_INPUT_STATE} */
+    public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
+    /** See {@code Manifest#BIND_INPUT_METHOD} */
+    public static final String BIND_INPUT_METHOD = "android.permission.BIND_INPUT_METHOD";
+    /** See {@code Manifest#BIND_MIDI_DEVICE_SERVICE} */
+    public static final String BIND_MIDI_DEVICE_SERVICE = "android.permission"
+            + ".BIND_MIDI_DEVICE_SERVICE";
+    /** See {@code Manifest#BIND_ACCESSIBILITY_SERVICE} */
+    public static final String BIND_ACCESSIBILITY_SERVICE =
+            "android.permission.BIND_ACCESSIBILITY_SERVICE";
+    /** See {@code Manifest#BIND_PRINT_SERVICE} */
+    public static final String BIND_PRINT_SERVICE = "android.permission.BIND_PRINT_SERVICE";
+    /** See {@code Manifest#BIND_PRINT_RECOMMENDATION_SERVICE} */
+    public static final String BIND_PRINT_RECOMMENDATION_SERVICE = "android.permission.BIND_PRINT_RECOMMENDATION_SERVICE";
+    /** See {@code Manifest#READ_PRINT_SERVICES} */
+    public static final String READ_PRINT_SERVICES = "android.permission.READ_PRINT_SERVICES";
+    /** See {@code Manifest#READ_PRINT_SERVICE_RECOMMENDATIONS} */
+    public static final String READ_PRINT_SERVICE_RECOMMENDATIONS = "android.permission"
+            + ".READ_PRINT_SERVICE_RECOMMENDATIONS";
+    /** See {@code Manifest#BIND_NFC_SERVICE} */
+    public static final String BIND_NFC_SERVICE = "android.permission.BIND_NFC_SERVICE";
+    /** See {@code Manifest#BIND_QUICK_ACCESS_WALLET_SERVICE} */
+    public static final String BIND_QUICK_ACCESS_WALLET_SERVICE =
+            "android.permission.BIND_QUICK_ACCESS_WALLET_SERVICE";
+    /** See {@code Manifest#BIND_PRINT_SPOOLER_SERVICE} */
+    public static final String BIND_PRINT_SPOOLER_SERVICE = "android.permission"
+            + ".BIND_PRINT_SPOOLER_SERVICE";
+    /** See {@code Manifest#BIND_COMPANION_DEVICE_MANAGER_SERVICE} */
+    public static final String BIND_COMPANION_DEVICE_MANAGER_SERVICE =
+            "android.permission.BIND_COMPANION_DEVICE_MANAGER_SERVICE";
+    /** See {@code Manifest#BIND_COMPANION_DEVICE_SERVICE} */
+    public static final String BIND_COMPANION_DEVICE_SERVICE = "android.permission"
+            + ".BIND_COMPANION_DEVICE_SERVICE";
+    /** See {@code Manifest#BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE} */
+    public static final String BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE = "android.permission"
+            + ".BIND_RUNTIME_PERMISSION_PRESENTER_SERVICE";
+    /** See {@code Manifest#BIND_TEXT_SERVICE} */
+    public static final String BIND_TEXT_SERVICE = "android.permission.BIND_TEXT_SERVICE";
+    /** See {@code Manifest#BIND_ATTENTION_SERVICE} */
+    public static final String BIND_ATTENTION_SERVICE = "android.permission"
+            + ".BIND_ATTENTION_SERVICE";
+    /** See {@code Manifest#BIND_ROTATION_RESOLVER_SERVICE} */
+    public static final String BIND_ROTATION_RESOLVER_SERVICE = "android.permission"
+            + ".BIND_ROTATION_RESOLVER_SERVICE";
+    /** See {@code Manifest#BIND_VPN_SERVICE} */
+    public static final String BIND_VPN_SERVICE = "android.permission.BIND_VPN_SERVICE";
+    /** See {@code Manifest#BIND_WALLPAPER} */
+    public static final String BIND_WALLPAPER = "android.permission.BIND_WALLPAPER";
+    /** See {@code Manifest#BIND_GAME_SERVICE} */
+    public static final String BIND_GAME_SERVICE = "android.permission.BIND_GAME_SERVICE";
+    /** See {@code Manifest#BIND_VOICE_INTERACTION} */
+    public static final String BIND_VOICE_INTERACTION = "android.permission"
+            + ".BIND_VOICE_INTERACTION";
+    /** See {@code Manifest#BIND_HOTWORD_DETECTION_SERVICE} */
+    public static final String BIND_HOTWORD_DETECTION_SERVICE = "android.permission"
+            + ".BIND_HOTWORD_DETECTION_SERVICE";
+    /** See {@code Manifest#MANAGE_HOTWORD_DETECTION} */
+    public static final String MANAGE_HOTWORD_DETECTION = "android.permission"
+            + ".MANAGE_HOTWORD_DETECTION";
+    /** See {@code Manifest#BIND_AUTOFILL_SERVICE} */
+    public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE";
+    /** See {@code Manifest#BIND_AUTOFILL} */
+    public static final String BIND_AUTOFILL = "android.permission.BIND_AUTOFILL";
+    /** See {@code Manifest#BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE} */
+    public static final String BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE = "android.permission"
+            + ".BIND_AUTOFILL_FIELD_CLASSIFICATION_SERVICE";
+    /** See {@code Manifest#BIND_INLINE_SUGGESTION_RENDER_SERVICE} */
+    public static final String BIND_INLINE_SUGGESTION_RENDER_SERVICE =
+            "android.permission.BIND_INLINE_SUGGESTION_RENDER_SERVICE";
+    /** See {@code Manifest#BIND_TEXTCLASSIFIER_SERVICE} */
+    public static final String BIND_TEXTCLASSIFIER_SERVICE =
+            "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
+    /** See {@code Manifest#BIND_CONTENT_CAPTURE_SERVICE} */
+    public static final String BIND_CONTENT_CAPTURE_SERVICE = "android.permission"
+            + ".BIND_CONTENT_CAPTURE_SERVICE";
+    /** See {@code Manifest#BIND_TRANSLATION_SERVICE} */
+    public static final String BIND_TRANSLATION_SERVICE =
+            "android.permission.BIND_TRANSLATION_SERVICE";
+    /** See {@code Manifest#MANAGE_UI_TRANSLATION} */
+    public static final String MANAGE_UI_TRANSLATION =  "android.permission.MANAGE_UI_TRANSLATION";
+    /** See {@code Manifest#BIND_CONTENT_SUGGESTIONS_SERVICE} */
+    public static final String BIND_CONTENT_SUGGESTIONS_SERVICE = "android.permission"
+            + ".BIND_CONTENT_SUGGESTIONS_SERVICE";
+    /** See {@code Manifest#BIND_MUSIC_RECOGNITION_SERVICE} */
+    public static final String BIND_MUSIC_RECOGNITION_SERVICE =
+            "android.permission.BIND_MUSIC_RECOGNITION_SERVICE";
+    /** See {@code Manifest#BIND_AUGMENTED_AUTOFILL_SERVICE} */
+    public static final String BIND_AUGMENTED_AUTOFILL_SERVICE = "android.permission"
+            + ".BIND_AUGMENTED_AUTOFILL_SERVICE";
+    /** See {@code Manifest#MANAGE_VOICE_KEYPHRASES} */
+    public static final String MANAGE_VOICE_KEYPHRASES = "android.permission"
+            + ".MANAGE_VOICE_KEYPHRASES";
+    /** See {@code Manifest#KEYPHRASE_ENROLLMENT_APPLICATION} */
+    public static final String KEYPHRASE_ENROLLMENT_APPLICATION =
+            "android.permission.KEYPHRASE_ENROLLMENT_APPLICATION";
+    /** See {@code Manifest#BIND_REMOTE_DISPLAY} */
+    public static final String BIND_REMOTE_DISPLAY = "android.permission.BIND_REMOTE_DISPLAY";
+    /** See {@code Manifest#BIND_TV_INPUT} */
+    public static final String BIND_TV_INPUT = "android.permission.BIND_TV_INPUT";
+    /** See {@code Manifest#BIND_TV_REMOTE_SERVICE} */
+    public static final String BIND_TV_REMOTE_SERVICE = "android.permission"
+            + ".BIND_TV_REMOTE_SERVICE";
+    /** See {@code Manifest#TV_VIRTUAL_REMOTE_CONTROLLER} */
+    public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission"
+            + ".TV_VIRTUAL_REMOTE_CONTROLLER";
+    /** See {@code Manifest#CHANGE_HDMI_CEC_ACTIVE_SOURCE} */
+    public static final String CHANGE_HDMI_CEC_ACTIVE_SOURCE = "android.permission"
+            + ".CHANGE_HDMI_CEC_ACTIVE_SOURCE";
+    /** See {@code Manifest#MODIFY_PARENTAL_CONTROLS} */
+    public static final String MODIFY_PARENTAL_CONTROLS = "android.permission"
+            + ".MODIFY_PARENTAL_CONTROLS";
+    /** See {@code Manifest#READ_CONTENT_RATING_SYSTEMS} */
+    public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission"
+            + ".READ_CONTENT_RATING_SYSTEMS";
+    /** See {@code Manifest#NOTIFY_TV_INPUTS} */
+    public static final String NOTIFY_TV_INPUTS = "android.permission.NOTIFY_TV_INPUTS";
+    /** See {@code Manifest#TUNER_RESOURCE_ACCESS} */
+    public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS";
+    /** See {@code Manifest#MEDIA_RESOURCE_OVERRIDE_PID} */
+    public static final String MEDIA_RESOURCE_OVERRIDE_PID = "android.permission"
+            + ".MEDIA_RESOURCE_OVERRIDE_PID";
+    /** See {@code Manifest#REGISTER_MEDIA_RESOURCE_OBSERVER} */
+    public static final String REGISTER_MEDIA_RESOURCE_OBSERVER = "android.permission"
+            + ".REGISTER_MEDIA_RESOURCE_OBSERVER";
+    /** See {@code Manifest#BIND_ROUTE_PROVIDER} */
+    public static final String BIND_ROUTE_PROVIDER = "android.permission.BIND_ROUTE_PROVIDER";
+    /** See {@code Manifest#BIND_DEVICE_ADMIN} */
+    public static final String BIND_DEVICE_ADMIN = "android.permission.BIND_DEVICE_ADMIN";
+    /** See {@code Manifest#MANAGE_DEVICE_ADMINS} */
+    public static final String MANAGE_DEVICE_ADMINS = "android.permission.MANAGE_DEVICE_ADMINS";
+    /** See {@code Manifest#RESET_PASSWORD} */
+    public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
+    /** See {@code Manifest#LOCK_DEVICE} */
+    public static final String LOCK_DEVICE = "android.permission.LOCK_DEVICE";
+    /** See {@code Manifest#SET_ORIENTATION} */
+    public static final String SET_ORIENTATION = "android.permission.SET_ORIENTATION";
+    /** See {@code Manifest#SET_POINTER_SPEED} */
+    public static final String SET_POINTER_SPEED = "android.permission.SET_POINTER_SPEED";
+    /** See {@code Manifest#SET_INPUT_CALIBRATION} */
+    public static final String SET_INPUT_CALIBRATION = "android.permission.SET_INPUT_CALIBRATION";
+    /** See {@code Manifest#SET_KEYBOARD_LAYOUT} */
+    public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+    /** See {@code Manifest#SCHEDULE_PRIORITIZED_ALARM} */
+    public static final String SCHEDULE_PRIORITIZED_ALARM =
+            "android.permission.SCHEDULE_PRIORITIZED_ALARM";
+    /** See {@code Manifest#SCHEDULE_EXACT_ALARM} */
+    public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
+    /** See {@code Manifest#TABLET_MODE} */
+    public static final String TABLET_MODE = "android.permission.TABLET_MODE";
+    /** See {@code Manifest#REQUEST_INSTALL_PACKAGES} */
+    public static final String REQUEST_INSTALL_PACKAGES = "android.permission"
+            + ".REQUEST_INSTALL_PACKAGES";
+    /** See {@code Manifest#REQUEST_DELETE_PACKAGES} */
+    public static final String REQUEST_DELETE_PACKAGES = "android.permission"
+            + ".REQUEST_DELETE_PACKAGES";
+    /** See {@code Manifest#INSTALL_PACKAGES} */
+    public static final String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
+    /** See {@code Manifest#INSTALL_SELF_UPDATES} */
+    public static final String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
+    /** See {@code Manifest#INSTALL_PACKAGE_UPDATES} */
+    public static final String INSTALL_PACKAGE_UPDATES = "android.permission"
+            + ".INSTALL_PACKAGE_UPDATES";
+    /** See {@code Manifest#INSTALL_EXISTING_PACKAGES} */
+    public static final String INSTALL_EXISTING_PACKAGES = "com.android.permission"
+            + ".INSTALL_EXISTING_PACKAGES";
+    /** See {@code Manifest#USE_INSTALLER_V2} */
+    public static final String USE_INSTALLER_V2 = "com.android.permission.USE_INSTALLER_V2";
+    /** See {@code Manifest#INSTALL_TEST_ONLY_PACKAGE} */
+    public static final String INSTALL_TEST_ONLY_PACKAGE = "android.permission"
+            + ".INSTALL_TEST_ONLY_PACKAGE";
+    /** See {@code Manifest#INSTALL_DPC_PACKAGES} */
+    public static final String INSTALL_DPC_PACKAGES = "android.permission.INSTALL_DPC_PACKAGES";
+    /** See {@code Manifest#USE_SYSTEM_DATA_LOADERS} */
+    public static final String USE_SYSTEM_DATA_LOADERS = "com.android.permission"
+            + ".USE_SYSTEM_DATA_LOADERS";
+    /** See {@code Manifest#CLEAR_APP_USER_DATA} */
+    public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA";
+    /** See {@code Manifest#GET_APP_GRANTED_URI_PERMISSIONS} */
+    public static final String GET_APP_GRANTED_URI_PERMISSIONS = "android.permission"
+            + ".GET_APP_GRANTED_URI_PERMISSIONS";
+    /** See {@code Manifest#CLEAR_APP_GRANTED_URI_PERMISSIONS} */
+    public static final String CLEAR_APP_GRANTED_URI_PERMISSIONS =
+            "android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS";
+    /** See {@code Manifest#MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS} */
+    public static final String MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS =
+            "android.permission.MANAGE_SCOPED_ACCESS_DIRECTORY_PERMISSIONS";
+    /** See {@code Manifest#FORCE_PERSISTABLE_URI_PERMISSIONS} */
+    public static final String FORCE_PERSISTABLE_URI_PERMISSIONS = "android.permission"
+            + ".FORCE_PERSISTABLE_URI_PERMISSIONS";
+    /** See {@code Manifest#DELETE_CACHE_FILES} */
+    public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
+    /** See {@code Manifest#INTERNAL_DELETE_CACHE_FILES} */
+    public static final String INTERNAL_DELETE_CACHE_FILES =
+            "android.permission.INTERNAL_DELETE_CACHE_FILES";
+    /** See {@code Manifest#DELETE_PACKAGES} */
+    public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
+    /** See {@code Manifest#MOVE_PACKAGE} */
+    public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
+    /** See {@code Manifest#KEEP_UNINSTALLED_PACKAGES} */
+    public static final String KEEP_UNINSTALLED_PACKAGES =
+            "android.permission.KEEP_UNINSTALLED_PACKAGES";
+    /** See {@code Manifest#CHANGE_COMPONENT_ENABLED_STATE} */
+    public static final String CHANGE_COMPONENT_ENABLED_STATE =
+            "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
+    /** See {@code Manifest#GRANT_RUNTIME_PERMISSIONS} */
+    public static final String GRANT_RUNTIME_PERMISSIONS =
+            "android.permission.GRANT_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#INSTALL_GRANT_RUNTIME_PERMISSIONS} */
+    public static final String INSTALL_GRANT_RUNTIME_PERMISSIONS = "android.permission"
+            + ".INSTALL_GRANT_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#REVOKE_RUNTIME_PERMISSIONS} */
+    public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission"
+            + ".REVOKE_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#GET_RUNTIME_PERMISSIONS} */
+    public static final String GET_RUNTIME_PERMISSIONS = "android.permission"
+            + ".GET_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#RESTORE_RUNTIME_PERMISSIONS} */
+    public static final String RESTORE_RUNTIME_PERMISSIONS = "android.permission"
+            + ".RESTORE_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#ADJUST_RUNTIME_PERMISSIONS_POLICY} */
+    public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission"
+            + ".ADJUST_RUNTIME_PERMISSIONS_POLICY";
+    /** See {@code Manifest#UPGRADE_RUNTIME_PERMISSIONS} */
+    public static final String UPGRADE_RUNTIME_PERMISSIONS =
+            "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+    /** See {@code Manifest#WHITELIST_RESTRICTED_PERMISSIONS} */
+    public static final String WHITELIST_RESTRICTED_PERMISSIONS = "android.permission"
+            + ".WHITELIST_RESTRICTED_PERMISSIONS";
+    /** See {@code Manifest#WHITELIST_AUTO_REVOKE_PERMISSIONS} */
+    public static final String WHITELIST_AUTO_REVOKE_PERMISSIONS = "android.permission"
+            + ".WHITELIST_AUTO_REVOKE_PERMISSIONS";
+    /** See {@code Manifest#OBSERVE_GRANT_REVOKE_PERMISSIONS} */
+    public static final String OBSERVE_GRANT_REVOKE_PERMISSIONS =
+            "android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS";
+    /** See {@code Manifest#MANAGE_ONE_TIME_PERMISSION_SESSIONS} */
+    public static final String MANAGE_ONE_TIME_PERMISSION_SESSIONS =
+            "android.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS";
+    /** See {@code Manifest#MANAGE_ROLE_HOLDERS} */
+    public static final String MANAGE_ROLE_HOLDERS = "android.permission.MANAGE_ROLE_HOLDERS";
+    /** See {@code Manifest#BYPASS_ROLE_QUALIFICATION} */
+    public static final String BYPASS_ROLE_QUALIFICATION = "android.permission"
+            + ".BYPASS_ROLE_QUALIFICATION";
+    /** See {@code Manifest#OBSERVE_ROLE_HOLDERS} */
+    public static final String OBSERVE_ROLE_HOLDERS = "android.permission.OBSERVE_ROLE_HOLDERS";
+    /** See {@code Manifest#MANAGE_COMPANION_DEVICES} */
+    public static final String MANAGE_COMPANION_DEVICES =
+            "android.permission.MANAGE_COMPANION_DEVICES";
+    /** See {@code Manifest#REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE} */
+    public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE =
+            "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
+    /** See {@code Manifest#DELIVER_COMPANION_MESSAGES} */
+    public static final String DELIVER_COMPANION_MESSAGES = "android.permission"
+            + ".DELIVER_COMPANION_MESSAGES";
+    /** See {@code Manifest#ACCESS_SURFACE_FLINGER} */
+    public static final String ACCESS_SURFACE_FLINGER = "android.permission.ACCESS_SURFACE_FLINGER";
+    /** See {@code Manifest#ROTATE_SURFACE_FLINGER} */
+    public static final String ROTATE_SURFACE_FLINGER = "android.permission"
+            + ".ROTATE_SURFACE_FLINGER";
+    /** See {@code Manifest#READ_FRAME_BUFFER} */
+    public static final String READ_FRAME_BUFFER = "android.permission.READ_FRAME_BUFFER";
+    /** See {@code Manifest#ACCESS_INPUT_FLINGER} */
+    public static final String ACCESS_INPUT_FLINGER = "android.permission.ACCESS_INPUT_FLINGER";
+    /** See {@code Manifest#DISABLE_INPUT_DEVICE} */
+    public static final String DISABLE_INPUT_DEVICE = "android.permission.DISABLE_INPUT_DEVICE";
+    /** See {@code Manifest#CONFIGURE_WIFI_DISPLAY} */
+    public static final String CONFIGURE_WIFI_DISPLAY =
+            "android.permission.CONFIGURE_WIFI_DISPLAY";
+    /** See {@code Manifest#CONTROL_WIFI_DISPLAY} */
+    public static final String CONTROL_WIFI_DISPLAY = "android.permission.CONTROL_WIFI_DISPLAY";
+    /** See {@code Manifest#CONFIGURE_DISPLAY_COLOR_MODE} */
+    public static final String CONFIGURE_DISPLAY_COLOR_MODE =
+            "android.permission.CONFIGURE_DISPLAY_COLOR_MODE";
+    /** See {@code Manifest#CONTROL_DEVICE_LIGHTS} */
+    public static final String CONTROL_DEVICE_LIGHTS = "android.permission.CONTROL_DEVICE_LIGHTS";
+    /** See {@code Manifest#CONTROL_DISPLAY_SATURATION} */
+    public static final String CONTROL_DISPLAY_SATURATION = "android.permission"
+            + ".CONTROL_DISPLAY_SATURATION";
+    /** See {@code Manifest#CONTROL_DISPLAY_COLOR_TRANSFORMS} */
+    public static final String CONTROL_DISPLAY_COLOR_TRANSFORMS =
+            "android.permission.CONTROL_DISPLAY_COLOR_TRANSFORMS";
+    /** See {@code Manifest#BRIGHTNESS_SLIDER_USAGE} */
+    public static final String BRIGHTNESS_SLIDER_USAGE = "android.permission"
+            + ".BRIGHTNESS_SLIDER_USAGE";
+    /** See {@code Manifest#ACCESS_AMBIENT_LIGHT_STATS} */
+    public static final String ACCESS_AMBIENT_LIGHT_STATS =
+            "android.permission.ACCESS_AMBIENT_LIGHT_STATS";
+    /** See {@code Manifest#CONFIGURE_DISPLAY_BRIGHTNESS} */
+    public static final String CONFIGURE_DISPLAY_BRIGHTNESS =
+            "android.permission.CONFIGURE_DISPLAY_BRIGHTNESS";
+    /** See {@code Manifest#CONTROL_DISPLAY_BRIGHTNESS} */
+    public static final String CONTROL_DISPLAY_BRIGHTNESS = "android.permission"
+            + ".CONTROL_DISPLAY_BRIGHTNESS";
+    /** See {@code Manifest#OVERRIDE_DISPLAY_MODE_REQUESTS} */
+    public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission"
+            + ".OVERRIDE_DISPLAY_MODE_REQUESTS";
+    /** See {@code Manifest#MODIFY_REFRESH_RATE_SWITCHING_TYPE} */
+    public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE =
+            "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE";
+    /** See {@code Manifest#CONTROL_VPN} */
+    public static final String CONTROL_VPN = "android.permission.CONTROL_VPN";
+    /** See {@code Manifest#CONTROL_ALWAYS_ON_VPN} */
+    public static final String CONTROL_ALWAYS_ON_VPN = "android.permission.CONTROL_ALWAYS_ON_VPN";
+    /** See {@code Manifest#CAPTURE_TUNER_AUDIO_INPUT} */
+    public static final String CAPTURE_TUNER_AUDIO_INPUT =
+            "android.permission.CAPTURE_TUNER_AUDIO_INPUT";
+    /** See {@code Manifest#CAPTURE_AUDIO_OUTPUT} */
+    public static final String CAPTURE_AUDIO_OUTPUT = "android.permission.CAPTURE_AUDIO_OUTPUT";
+    /** See {@code Manifest#CAPTURE_MEDIA_OUTPUT} */
+    public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT";
+    /** See {@code Manifest#CAPTURE_VOICE_COMMUNICATION_OUTPUT} */
+    public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission"
+            + ".CAPTURE_VOICE_COMMUNICATION_OUTPUT";
+    /** See {@code Manifest#CAPTURE_AUDIO_HOTWORD} */
+    public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD";
+    /** See {@code Manifest#SOUNDTRIGGER_DELEGATE_IDENTITY} */
+    public static final String SOUNDTRIGGER_DELEGATE_IDENTITY =
+            "android.permission.SOUNDTRIGGER_DELEGATE_IDENTITY";
+    /** See {@code Manifest#MODIFY_AUDIO_ROUTING} */
+    public static final String MODIFY_AUDIO_ROUTING = "android.permission.MODIFY_AUDIO_ROUTING";
+    /** See {@code Manifest#CALL_AUDIO_INTERCEPTION} */
+    public static final String CALL_AUDIO_INTERCEPTION =
+            "android.permission.CALL_AUDIO_INTERCEPTION";
+    /** See {@code Manifest#QUERY_AUDIO_STATE} */
+    public static final String QUERY_AUDIO_STATE = "android.permission.QUERY_AUDIO_STATE";
+    /** See {@code Manifest#MODIFY_DEFAULT_AUDIO_EFFECTS} */
+    public static final String MODIFY_DEFAULT_AUDIO_EFFECTS = "android.permission"
+            + ".MODIFY_DEFAULT_AUDIO_EFFECTS";
+    /** See {@code Manifest#DISABLE_SYSTEM_SOUND_EFFECTS} */
+    public static final String DISABLE_SYSTEM_SOUND_EFFECTS = "android.permission"
+            + ".DISABLE_SYSTEM_SOUND_EFFECTS";
+    /** See {@code Manifest#REMOTE_DISPLAY_PROVIDER} */
+    public static final String REMOTE_DISPLAY_PROVIDER =
+            "android.permission.REMOTE_DISPLAY_PROVIDER";
+    /** See {@code Manifest#CAPTURE_SECURE_VIDEO_OUTPUT} */
+    public static final String CAPTURE_SECURE_VIDEO_OUTPUT =
+            "android.permission.CAPTURE_SECURE_VIDEO_OUTPUT";
+    /** See {@code Manifest#MEDIA_CONTENT_CONTROL} */
+    public static final String MEDIA_CONTENT_CONTROL = "android.permission.MEDIA_CONTENT_CONTROL";
+    /** See {@code Manifest#SET_VOLUME_KEY_LONG_PRESS_LISTENER} */
+    public static final String SET_VOLUME_KEY_LONG_PRESS_LISTENER =
+            "android.permission.SET_VOLUME_KEY_LONG_PRESS_LISTENER";
+    /** See {@code Manifest#SET_MEDIA_KEY_LISTENER} */
+    public static final String SET_MEDIA_KEY_LISTENER = "android.permission"
+            + ".SET_MEDIA_KEY_LISTENER";
+    /** See {@code Manifest#BRICK} */
+    public static final String BRICK = "android.permission.BRICK";
+    /** See {@code Manifest#REBOOT} */
+    public static final String REBOOT = "android.permission.REBOOT";
+    /** See {@code Manifest#DEVICE_POWER} */
+    public static final String DEVICE_POWER = "android.permission.DEVICE_POWER";
+    /** See {@code Manifest#POWER_SAVER} */
+    public static final String POWER_SAVER = "android.permission.POWER_SAVER";
+    /** See {@code Manifest#BATTERY_PREDICTION} */
+    public static final String BATTERY_PREDICTION = "android.permission.BATTERY_PREDICTION";
+    /** See {@code Manifest#USER_ACTIVITY} */
+    public static final String USER_ACTIVITY = "android.permission.USER_ACTIVITY";
+    /** See {@code Manifest#NET_TUNNELING} */
+    public static final String NET_TUNNELING = "android.permission.NET_TUNNELING";
+    /** See {@code Manifest#FACTORY_TEST} */
+    public static final String FACTORY_TEST = "android.permission.FACTORY_TEST";
+    /** See {@code Manifest#BROADCAST_CLOSE_SYSTEM_DIALOGS} */
+    public static final String BROADCAST_CLOSE_SYSTEM_DIALOGS =
+            "android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS";
+    /** See {@code Manifest#BROADCAST_PACKAGE_REMOVED} */
+    public static final String BROADCAST_PACKAGE_REMOVED =
+            "android.permission.BROADCAST_PACKAGE_REMOVED";
+    /** See {@code Manifest#BROADCAST_SMS} */
+    public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
+    /** See {@code Manifest#BROADCAST_WAP_PUSH} */
+    public static final String BROADCAST_WAP_PUSH = "android.permission.BROADCAST_WAP_PUSH";
+    /** See {@code Manifest#BROADCAST_NETWORK_PRIVILEGED} */
+    public static final String BROADCAST_NETWORK_PRIVILEGED = "android.permission"
+            + ".BROADCAST_NETWORK_PRIVILEGED";
+    /** See {@code Manifest#MASTER_CLEAR} */
+    public static final String MASTER_CLEAR = "android.permission.MASTER_CLEAR";
+    /** See {@code Manifest#CALL_PRIVILEGED} */
+    public static final String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
+    /** See {@code Manifest#PERFORM_CDMA_PROVISIONING} */
+    public static final String PERFORM_CDMA_PROVISIONING =
+            "android.permission.PERFORM_CDMA_PROVISIONING";
+    /** See {@code Manifest#PERFORM_SIM_ACTIVATION} */
+    public static final String PERFORM_SIM_ACTIVATION = "android.permission.PERFORM_SIM_ACTIVATION";
+    /** See {@code Manifest#CONTROL_LOCATION_UPDATES} */
+    public static final String CONTROL_LOCATION_UPDATES =
+            "android.permission.CONTROL_LOCATION_UPDATES";
+    /** See {@code Manifest#ACCESS_CHECKIN_PROPERTIES} */
+    public static final String ACCESS_CHECKIN_PROPERTIES = "android.permission"
+            + ".ACCESS_CHECKIN_PROPERTIES";
+    /** See {@code Manifest#PACKAGE_USAGE_STATS} */
+    public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
+    /** See {@code Manifest#LOADER_USAGE_STATS} */
+    public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
+    /** See {@code Manifest#OBSERVE_APP_USAGE} */
+    public static final String OBSERVE_APP_USAGE = "android.permission.OBSERVE_APP_USAGE";
+    /** See {@code Manifest#CHANGE_APP_IDLE_STATE} */
+    public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE";
+    /** See {@code Manifest#CHANGE_APP_LAUNCH_TIME_ESTIMATE} */
+    public static final String CHANGE_APP_LAUNCH_TIME_ESTIMATE =
+            "android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE";
+    /** See {@code Manifest#CHANGE_DEVICE_IDLE_TEMP_WHITELIST} */
+    public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST =
+            "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST";
+    /** See {@code Manifest#REQUEST_IGNORE_BATTERY_OPTIMIZATIONS} */
+    public static final String REQUEST_IGNORE_BATTERY_OPTIMIZATIONS =
+            "android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS";
+    /** See {@code Manifest#BATTERY_STATS} */
+    public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
+    /** See {@code Manifest#STATSCOMPANION} */
+    public static final String STATSCOMPANION = "android.permission.STATSCOMPANION";
+    /** See {@code Manifest#REGISTER_STATS_PULL_ATOM} */
+    public static final String REGISTER_STATS_PULL_ATOM = "android.permission"
+            + ".REGISTER_STATS_PULL_ATOM";
+    /** See {@code Manifest#BACKUP} */
+    public static final String BACKUP = "android.permission.BACKUP";
+    /** See {@code Manifest#MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE} */
+    public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission"
+            + ".MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
+    /** See {@code Manifest#RECOVER_KEYSTORE} */
+    public static final String RECOVER_KEYSTORE = "android.permission.RECOVER_KEYSTORE";
+    /** See {@code Manifest#CONFIRM_FULL_BACKUP} */
+    public static final String CONFIRM_FULL_BACKUP = "android.permission.CONFIRM_FULL_BACKUP";
+    /** See {@code Manifest#BIND_REMOTEVIEWS} */
+    public static final String BIND_REMOTEVIEWS = "android.permission.BIND_REMOTEVIEWS";
+    /** See {@code Manifest#BIND_APPWIDGET} */
+    public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+    /** See {@code Manifest#MANAGE_SLICE_PERMISSIONS} */
+    public static final String MANAGE_SLICE_PERMISSIONS =
+            "android.permission.MANAGE_SLICE_PERMISSIONS";
+    /** See {@code Manifest#BIND_KEYGUARD_APPWIDGET} */
+    public static final String BIND_KEYGUARD_APPWIDGET = "android.permission"
+            + ".BIND_KEYGUARD_APPWIDGET";
+    /** See {@code Manifest#MODIFY_APPWIDGET_BIND_PERMISSIONS} */
+    public static final String MODIFY_APPWIDGET_BIND_PERMISSIONS =
+            "android.permission.MODIFY_APPWIDGET_BIND_PERMISSIONS";
+    /** See {@code Manifest#CHANGE_BACKGROUND_DATA_SETTING} */
+    public static final String CHANGE_BACKGROUND_DATA_SETTING =
+            "android.permission.CHANGE_BACKGROUND_DATA_SETTING";
+    /** See {@code Manifest#GLOBAL_SEARCH} */
+    public static final String GLOBAL_SEARCH = "android.permission.GLOBAL_SEARCH";
+    /** See {@code Manifest#GLOBAL_SEARCH_CONTROL} */
+    public static final String GLOBAL_SEARCH_CONTROL = "android.permission.GLOBAL_SEARCH_CONTROL";
+    /** See {@code Manifest#READ_SEARCH_INDEXABLES} */
+    public static final String READ_SEARCH_INDEXABLES = "android.permission"
+            + ".READ_SEARCH_INDEXABLES";
+    /** See {@code Manifest#BIND_SETTINGS_SUGGESTIONS_SERVICE} */
+    public static final String BIND_SETTINGS_SUGGESTIONS_SERVICE = "android.permission"
+            + ".BIND_SETTINGS_SUGGESTIONS_SERVICE";
+    /** See {@code Manifest#WRITE_SETTINGS_HOMEPAGE_DATA} */
+    public static final String WRITE_SETTINGS_HOMEPAGE_DATA = "android.permission"
+            + ".WRITE_SETTINGS_HOMEPAGE_DATA";
+    /** See {@code Manifest#LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK} */
+    public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission"
+            + ".LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
+    /** See {@code Manifest#ALLOW_PLACE_IN_MULTI_PANE_SETTINGS} */
+    public static final String ALLOW_PLACE_IN_MULTI_PANE_SETTINGS =
+            "android.permission.ALLOW_PLACE_IN_MULTI_PANE_SETTINGS";
+    /** See {@code Manifest#SET_WALLPAPER_COMPONENT} */
+    public static final String SET_WALLPAPER_COMPONENT = "android.permission"
+            + ".SET_WALLPAPER_COMPONENT";
+    /** See {@code Manifest#READ_DREAM_STATE} */
+    public static final String READ_DREAM_STATE = "android.permission.READ_DREAM_STATE";
+    /** See {@code Manifest#WRITE_DREAM_STATE} */
+    public static final String WRITE_DREAM_STATE = "android.permission.WRITE_DREAM_STATE";
+    /** See {@code Manifest#READ_DREAM_SUPPRESSION} */
+    public static final String READ_DREAM_SUPPRESSION = "android.permission"
+            + ".READ_DREAM_SUPPRESSION";
+    /** See {@code Manifest#ACCESS_CACHE_FILESYSTEM} */
+    public static final String ACCESS_CACHE_FILESYSTEM = "android.permission"
+            + ".ACCESS_CACHE_FILESYSTEM";
+    /** See {@code Manifest#COPY_PROTECTED_DATA} */
+    public static final String COPY_PROTECTED_DATA = "android.permission.COPY_PROTECTED_DATA";
+    /** See {@code Manifest#CRYPT_KEEPER} */
+    public static final String CRYPT_KEEPER = "android.permission.CRYPT_KEEPER";
+    /** See {@code Manifest#READ_NETWORK_USAGE_HISTORY} */
+    public static final String READ_NETWORK_USAGE_HISTORY =
+            "android.permission.READ_NETWORK_USAGE_HISTORY";
+    /** See {@code Manifest#MANAGE_NETWORK_POLICY} */
+    public static final String MANAGE_NETWORK_POLICY = "android.permission.MANAGE_NETWORK_POLICY";
+    /** See {@code Manifest#MODIFY_NETWORK_ACCOUNTING} */
+    public static final String MODIFY_NETWORK_ACCOUNTING =
+            "android.permission.MODIFY_NETWORK_ACCOUNTING";
+    /** See {@code Manifest#MANAGE_SUBSCRIPTION_PLANS} */
+    public static final String MANAGE_SUBSCRIPTION_PLANS = "android.permission"
+            + ".MANAGE_SUBSCRIPTION_PLANS";
+    /** See {@code Manifest#C2D_MESSAGE} */
+    public static final String C2D_MESSAGE = "android.intent.category.MASTER_CLEAR.permission"
+            + ".C2D_MESSAGE";
+    /** See {@code Manifest#PACKAGE_VERIFICATION_AGENT} */
+    public static final String PACKAGE_VERIFICATION_AGENT =
+            "android.permission.PACKAGE_VERIFICATION_AGENT";
+    /** See {@code Manifest#BIND_PACKAGE_VERIFIER} */
+    public static final String BIND_PACKAGE_VERIFIER = "android.permission.BIND_PACKAGE_VERIFIER";
+    /** See {@code Manifest#PACKAGE_ROLLBACK_AGENT} */
+    public static final String PACKAGE_ROLLBACK_AGENT = "android.permission.PACKAGE_ROLLBACK_AGENT";
+    /** See {@code Manifest#MANAGE_ROLLBACKS} */
+    public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
+    /** See {@code Manifest#TEST_MANAGE_ROLLBACKS} */
+    public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
+    /** See {@code Manifest#SET_HARMFUL_APP_WARNINGS} */
+    public static final String SET_HARMFUL_APP_WARNINGS =
+            "android.permission.SET_HARMFUL_APP_WARNINGS";
+    /** See {@code Manifest#INTENT_FILTER_VERIFICATION_AGENT} */
+    public static final String INTENT_FILTER_VERIFICATION_AGENT =
+            "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
+    /** See {@code Manifest#BIND_INTENT_FILTER_VERIFIER} */
+    public static final String BIND_INTENT_FILTER_VERIFIER = "android.permission"
+            + ".BIND_INTENT_FILTER_VERIFIER";
+    /** See {@code Manifest#DOMAIN_VERIFICATION_AGENT} */
+    public static final String DOMAIN_VERIFICATION_AGENT = "android.permission"
+            + ".DOMAIN_VERIFICATION_AGENT";
+    /** See {@code Manifest#BIND_DOMAIN_VERIFICATION_AGENT} */
+    public static final String BIND_DOMAIN_VERIFICATION_AGENT =
+            "android.permission.BIND_DOMAIN_VERIFICATION_AGENT";
+    /** See {@code Manifest#UPDATE_DOMAIN_VERIFICATION_USER_SELECTION} */
+    public static final String UPDATE_DOMAIN_VERIFICATION_USER_SELECTION =
+            "android.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION";
+    /** See {@code Manifest#SERIAL_PORT} */
+    public static final String SERIAL_PORT = "android.permission.SERIAL_PORT";
+    /** See {@code Manifest#ACCESS_CONTENT_PROVIDERS_EXTERNALLY} */
+    public static final String ACCESS_CONTENT_PROVIDERS_EXTERNALLY = "android.permission"
+            + ".ACCESS_CONTENT_PROVIDERS_EXTERNALLY";
+    /** See {@code Manifest#UPDATE_LOCK} */
+    public static final String UPDATE_LOCK = "android.permission.UPDATE_LOCK";
+    /** See {@code Manifest#REQUEST_NOTIFICATION_ASSISTANT_SERVICE} */
+    public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission"
+            + ".REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
+    /** See {@code Manifest#ACCESS_NOTIFICATIONS} */
+    public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
+    /** See {@code Manifest#ACCESS_NOTIFICATION_POLICY} */
+    public static final String ACCESS_NOTIFICATION_POLICY =
+            "android.permission.ACCESS_NOTIFICATION_POLICY";
+    /** See {@code Manifest#MANAGE_NOTIFICATIONS} */
+    public static final String MANAGE_NOTIFICATIONS = "android.permission.MANAGE_NOTIFICATIONS";
+    /** See {@code Manifest#MANAGE_NOTIFICATION_LISTENERS} */
+    public static final String MANAGE_NOTIFICATION_LISTENERS =
+            "android.permission.MANAGE_NOTIFICATION_LISTENERS";
+    /** See {@code Manifest#USE_COLORIZED_NOTIFICATIONS} */
+    public static final String USE_COLORIZED_NOTIFICATIONS =
+            "android.permission.USE_COLORIZED_NOTIFICATIONS";
+    /** See {@code Manifest#ACCESS_KEYGUARD_SECURE_STORAGE} */
+    public static final String ACCESS_KEYGUARD_SECURE_STORAGE = "android.permission"
+            + ".ACCESS_KEYGUARD_SECURE_STORAGE";
+    /** See {@code Manifest#SET_INITIAL_LOCK} */
+    public static final String SET_INITIAL_LOCK = "android.permission.SET_INITIAL_LOCK";
+    /** See {@code Manifest#SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS} */
+    public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS =
+            "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
+    /** See {@code Manifest#MANAGE_FINGERPRINT} */
+    public static final String MANAGE_FINGERPRINT = "android.permission.MANAGE_FINGERPRINT";
+    /** See {@code Manifest#RESET_FINGERPRINT_LOCKOUT} */
+    public static final String RESET_FINGERPRINT_LOCKOUT = "android.permission"
+            + ".RESET_FINGERPRINT_LOCKOUT";
+    /** See {@code Manifest#TEST_BIOMETRIC} */
+    public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
+    /** See {@code Manifest#MANAGE_BIOMETRIC} */
+    public static final String MANAGE_BIOMETRIC = "android.permission.MANAGE_BIOMETRIC";
+    /** See {@code Manifest#USE_BIOMETRIC_INTERNAL} */
+    public static final String USE_BIOMETRIC_INTERNAL = "android.permission"
+            + ".USE_BIOMETRIC_INTERNAL";
+    /** See {@code Manifest#MANAGE_BIOMETRIC_DIALOG} */
+    public static final String MANAGE_BIOMETRIC_DIALOG =
+            "android.permission.MANAGE_BIOMETRIC_DIALOG";
+    /** See {@code Manifest#CONTROL_KEYGUARD} */
+    public static final String CONTROL_KEYGUARD = "android.permission.CONTROL_KEYGUARD";
+    /** See {@code Manifest#CONTROL_KEYGUARD_SECURE_NOTIFICATIONS} */
+    public static final String CONTROL_KEYGUARD_SECURE_NOTIFICATIONS = "android.permission"
+            + ".CONTROL_KEYGUARD_SECURE_NOTIFICATIONS";
+    /** See {@code Manifest#TRUST_LISTENER} */
+    public static final String TRUST_LISTENER = "android.permission.TRUST_LISTENER";
+    /** See {@code Manifest#PROVIDE_TRUST_AGENT} */
+    public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
+    /** See {@code Manifest#SHOW_KEYGUARD_MESSAGE} */
+    public static final String SHOW_KEYGUARD_MESSAGE = "android.permission.SHOW_KEYGUARD_MESSAGE";
+    /** See {@code Manifest#LAUNCH_TRUST_AGENT_SETTINGS} */
+    public static final String LAUNCH_TRUST_AGENT_SETTINGS = "android.permission"
+            + ".LAUNCH_TRUST_AGENT_SETTINGS";
+    /** See {@code Manifest#BIND_TRUST_AGENT} */
+    public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
+    /** See {@code Manifest#BIND_NOTIFICATION_LISTENER_SERVICE} */
+    public static final String BIND_NOTIFICATION_LISTENER_SERVICE =
+            "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE";
+    /** See {@code Manifest#BIND_NOTIFICATION_ASSISTANT_SERVICE} */
+    public static final String BIND_NOTIFICATION_ASSISTANT_SERVICE =
+            "android.permission.BIND_NOTIFICATION_ASSISTANT_SERVICE";
+    /** See {@code Manifest#BIND_CHOOSER_TARGET_SERVICE} */
+    public static final String BIND_CHOOSER_TARGET_SERVICE =
+            "android.permission.BIND_CHOOSER_TARGET_SERVICE";
+    /** See {@code Manifest#PROVIDE_RESOLVER_RANKER_SERVICE} */
+    public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission"
+            + ".PROVIDE_RESOLVER_RANKER_SERVICE";
+    /** See {@code Manifest#BIND_RESOLVER_RANKER_SERVICE} */
+    public static final String BIND_RESOLVER_RANKER_SERVICE =
+            "android.permission.BIND_RESOLVER_RANKER_SERVICE";
+    /** See {@code Manifest#BIND_CONDITION_PROVIDER_SERVICE} */
+    public static final String BIND_CONDITION_PROVIDER_SERVICE =
+            "android.permission.BIND_CONDITION_PROVIDER_SERVICE";
+    /** See {@code Manifest#BIND_DREAM_SERVICE} */
+    public static final String BIND_DREAM_SERVICE = "android.permission.BIND_DREAM_SERVICE";
+    /** See {@code Manifest#BIND_CACHE_QUOTA_SERVICE} */
+    public static final String BIND_CACHE_QUOTA_SERVICE = "android.permission"
+            + ".BIND_CACHE_QUOTA_SERVICE";
+    /** See {@code Manifest#INVOKE_CARRIER_SETUP} */
+    public static final String INVOKE_CARRIER_SETUP = "android.permission.INVOKE_CARRIER_SETUP";
+    /** See {@code Manifest#ACCESS_NETWORK_CONDITIONS} */
+    public static final String ACCESS_NETWORK_CONDITIONS =
+            "android.permission.ACCESS_NETWORK_CONDITIONS";
+    /** See {@code Manifest#ACCESS_DRM_CERTIFICATES} */
+    public static final String ACCESS_DRM_CERTIFICATES =
+            "android.permission.ACCESS_DRM_CERTIFICATES";
+    /** See {@code Manifest#MANAGE_MEDIA_PROJECTION} */
+    public static final String MANAGE_MEDIA_PROJECTION =
+            "android.permission.MANAGE_MEDIA_PROJECTION";
+    /** See {@code Manifest#READ_INSTALL_SESSIONS} */
+    public static final String READ_INSTALL_SESSIONS = "android.permission.READ_INSTALL_SESSIONS";
+    /** See {@code Manifest#REMOVE_DRM_CERTIFICATES} */
+    public static final String REMOVE_DRM_CERTIFICATES = "android.permission"
+            + ".REMOVE_DRM_CERTIFICATES";
+    /** See {@code Manifest#BIND_CARRIER_MESSAGING_SERVICE} */
+    public static final String BIND_CARRIER_MESSAGING_SERVICE =
+            "android.permission.BIND_CARRIER_MESSAGING_SERVICE";
+    /** See {@code Manifest#ACCESS_VOICE_INTERACTION_SERVICE} */
+    public static final String ACCESS_VOICE_INTERACTION_SERVICE =
+            "android.permission.ACCESS_VOICE_INTERACTION_SERVICE";
+    /** See {@code Manifest#BIND_CARRIER_SERVICES} */
+    public static final String BIND_CARRIER_SERVICES = "android.permission.BIND_CARRIER_SERVICES";
+    /** See {@code Manifest#START_VIEW_PERMISSION_USAGE} */
+    public static final String START_VIEW_PERMISSION_USAGE = "android.permission"
+            + ".START_VIEW_PERMISSION_USAGE";
+    /** See {@code Manifest#QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT} */
+    public static final String QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT = "android.permission"
+            + ".QUERY_DO_NOT_ASK_CREDENTIALS_ON_BOOT";
+    /** See {@code Manifest#KILL_UID} */
+    public static final String KILL_UID = "android.permission.KILL_UID";
+    /** See {@code Manifest#LOCAL_MAC_ADDRESS} */
+    public static final String LOCAL_MAC_ADDRESS = "android.permission.LOCAL_MAC_ADDRESS";
+    /** See {@code Manifest#PEERS_MAC_ADDRESS} */
+    public static final String PEERS_MAC_ADDRESS = "android.permission.PEERS_MAC_ADDRESS";
+    /** See {@code Manifest#DISPATCH_NFC_MESSAGE} */
+    public static final String DISPATCH_NFC_MESSAGE = "android.permission.DISPATCH_NFC_MESSAGE";
+    /** See {@code Manifest#MODIFY_DAY_NIGHT_MODE} */
+    public static final String MODIFY_DAY_NIGHT_MODE = "android.permission.MODIFY_DAY_NIGHT_MODE";
+    /** See {@code Manifest#ENTER_CAR_MODE_PRIORITIZED} */
+    public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission"
+            + ".ENTER_CAR_MODE_PRIORITIZED";
+    /** See {@code Manifest#HANDLE_CAR_MODE_CHANGES} */
+    public static final String HANDLE_CAR_MODE_CHANGES = "android.permission"
+            + ".HANDLE_CAR_MODE_CHANGES";
+    /** See {@code Manifest#SEND_CATEGORY_CAR_NOTIFICATIONS} */
+    public static final String SEND_CATEGORY_CAR_NOTIFICATIONS =
+            "android.permission.SEND_CATEGORY_CAR_NOTIFICATIONS";
+    /** See {@code Manifest#ACCESS_INSTANT_APPS} */
+    public static final String ACCESS_INSTANT_APPS = "android.permission.ACCESS_INSTANT_APPS";
+    /** See {@code Manifest#VIEW_INSTANT_APPS} */
+    public static final String VIEW_INSTANT_APPS = "android.permission.VIEW_INSTANT_APPS";
+    /** See {@code Manifest#WRITE_COMMUNAL_STATE} */
+    public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
+    /** See {@code Manifest#READ_COMMUNAL_STATE} */
+    public static final String READ_COMMUNAL_STATE = "android.permission.READ_COMMUNAL_STATE";
+    /** See {@code Manifest#MANAGE_BIND_INSTANT_SERVICE} */
+    public static final String MANAGE_BIND_INSTANT_SERVICE =
+            "android.permission.MANAGE_BIND_INSTANT_SERVICE";
+    /** See {@code Manifest#RECEIVE_MEDIA_RESOURCE_USAGE} */
+    public static final String RECEIVE_MEDIA_RESOURCE_USAGE = "android.permission.RECEIVE_MEDIA_RESOURCE_USAGE";
+    /** See {@code Manifest#MANAGE_SOUND_TRIGGER} */
+    public static final String MANAGE_SOUND_TRIGGER = "android.permission.MANAGE_SOUND_TRIGGER";
+    /** See {@code Manifest#SOUND_TRIGGER_RUN_IN_BATTERY_SAVER} */
+    public static final String SOUND_TRIGGER_RUN_IN_BATTERY_SAVER = "android.permission"
+            + ".SOUND_TRIGGER_RUN_IN_BATTERY_SAVER";
+    /** See {@code Manifest#BIND_SOUND_TRIGGER_DETECTION_SERVICE} */
+    public static final String BIND_SOUND_TRIGGER_DETECTION_SERVICE = "android.permission"
+            + ".BIND_SOUND_TRIGGER_DETECTION_SERVICE";
+    /** See {@code Manifest#DISPATCH_PROVISIONING_MESSAGE} */
+    public static final String DISPATCH_PROVISIONING_MESSAGE = "android.permission"
+            + ".DISPATCH_PROVISIONING_MESSAGE";
+    /** See {@code Manifest#READ_BLOCKED_NUMBERS} */
+    public static final String READ_BLOCKED_NUMBERS = "android.permission.READ_BLOCKED_NUMBERS";
+    /** See {@code Manifest#WRITE_BLOCKED_NUMBERS} */
+    public static final String WRITE_BLOCKED_NUMBERS = "android.permission.WRITE_BLOCKED_NUMBERS";
+    /** See {@code Manifest#BIND_VR_LISTENER_SERVICE} */
+    public static final String BIND_VR_LISTENER_SERVICE = "android.permission"
+            + ".BIND_VR_LISTENER_SERVICE";
+    /** See {@code Manifest#RESTRICTED_VR_ACCESS} */
+    public static final String RESTRICTED_VR_ACCESS = "android.permission.RESTRICTED_VR_ACCESS";
+    /** See {@code Manifest#ACCESS_VR_MANAGER} */
+    public static final String ACCESS_VR_MANAGER = "android.permission.ACCESS_VR_MANAGER";
+    /** See {@code Manifest#ACCESS_VR_STATE} */
+    public static final String ACCESS_VR_STATE = "android.permission.ACCESS_VR_STATE";
+    /** See {@code Manifest#UPDATE_LOCK_TASK_PACKAGES} */
+    public static final String UPDATE_LOCK_TASK_PACKAGES =
+            "android.permission.UPDATE_LOCK_TASK_PACKAGES";
+    /** See {@code Manifest#SUBSTITUTE_NOTIFICATION_APP_NAME} */
+    public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission"
+            + ".SUBSTITUTE_NOTIFICATION_APP_NAME";
+    /** See {@code Manifest#NOTIFICATION_DURING_SETUP} */
+    public static final String NOTIFICATION_DURING_SETUP =
+            "android.permission.NOTIFICATION_DURING_SETUP";
+    /** See {@code Manifest#MANAGE_AUTO_FILL} */
+    public static final String MANAGE_AUTO_FILL = "android.permission.MANAGE_AUTO_FILL";
+    /** See {@code Manifest#MANAGE_CONTENT_CAPTURE} */
+    public static final String MANAGE_CONTENT_CAPTURE = "android.permission.MANAGE_CONTENT_CAPTURE";
+    /** See {@code Manifest#MANAGE_ROTATION_RESOLVER} */
+    public static final String MANAGE_ROTATION_RESOLVER = "android.permission"
+            + ".MANAGE_ROTATION_RESOLVER";
+    /** See {@code Manifest#MANAGE_MUSIC_RECOGNITION} */
+    public static final String MANAGE_MUSIC_RECOGNITION =
+            "android.permission.MANAGE_MUSIC_RECOGNITION";
+    /** See {@code Manifest#MANAGE_SPEECH_RECOGNITION} */
+    public static final String MANAGE_SPEECH_RECOGNITION =
+            "android.permission.MANAGE_SPEECH_RECOGNITION";
+    /** See {@code Manifest#MANAGE_CONTENT_SUGGESTIONS} */
+    public static final String MANAGE_CONTENT_SUGGESTIONS = "android.permission"
+            + ".MANAGE_CONTENT_SUGGESTIONS";
+    /** See {@code Manifest#MANAGE_APP_PREDICTIONS} */
+    public static final String MANAGE_APP_PREDICTIONS = "android.permission.MANAGE_APP_PREDICTIONS";
+    /** See {@code Manifest#MANAGE_SEARCH_UI} */
+    public static final String MANAGE_SEARCH_UI = "android.permission.MANAGE_SEARCH_UI";
+    /** See {@code Manifest#MANAGE_SMARTSPACE} */
+    public static final String MANAGE_SMARTSPACE = "android.permission.MANAGE_SMARTSPACE";
+    /** See {@code Manifest#MODIFY_THEME_OVERLAY} */
+    public static final String MODIFY_THEME_OVERLAY = "android.permission.MODIFY_THEME_OVERLAY";
+    /** See {@code Manifest#INSTANT_APP_FOREGROUND_SERVICE} */
+    public static final String INSTANT_APP_FOREGROUND_SERVICE =
+            "android.permission.INSTANT_APP_FOREGROUND_SERVICE";
+    /** See {@code Manifest#FOREGROUND_SERVICE} */
+    public static final String FOREGROUND_SERVICE = "android.permission.FOREGROUND_SERVICE";
+    /** See {@code Manifest#ACCESS_SHORTCUTS} */
+    public static final String ACCESS_SHORTCUTS = "android.permission.ACCESS_SHORTCUTS";
+    /** See {@code Manifest#UNLIMITED_SHORTCUTS_API_CALLS} */
+    public static final String UNLIMITED_SHORTCUTS_API_CALLS =
+            "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
+    /** See {@code Manifest#READ_RUNTIME_PROFILES} */
+    public static final String READ_RUNTIME_PROFILES = "android.permission.READ_RUNTIME_PROFILES";
+    /** See {@code Manifest#MANAGE_AUDIO_POLICY} */
+    public static final String MANAGE_AUDIO_POLICY = "android.permission.MANAGE_AUDIO_POLICY";
+    /** See {@code Manifest#MODIFY_QUIET_MODE} */
+    public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
+    /** See {@code Manifest#MANAGE_CAMERA} */
+    public static final String MANAGE_CAMERA = "android.permission.MANAGE_CAMERA";
+    /** See {@code Manifest#CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS} */
+    public static final String CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS =
+            "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS";
+    /** See {@code Manifest#WATCH_APPOPS} */
+    public static final String WATCH_APPOPS = "android.permission.WATCH_APPOPS";
+    /** See {@code Manifest#DISABLE_HIDDEN_API_CHECKS} */
+    public static final String DISABLE_HIDDEN_API_CHECKS =
+            "android.permission.DISABLE_HIDDEN_API_CHECKS";
+    /** See {@code Manifest#MONITOR_DEFAULT_SMS_PACKAGE} */
+    public static final String MONITOR_DEFAULT_SMS_PACKAGE =
+            "android.permission.MONITOR_DEFAULT_SMS_PACKAGE";
+    /** See {@code Manifest#BIND_CARRIER_MESSAGING_CLIENT_SERVICE} */
+    public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE =
+            "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE";
+    /** See {@code Manifest#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} */
+    public static final String BIND_EXPLICIT_HEALTH_CHECK_SERVICE =
+            "android.permission.BIND_EXPLICIT_HEALTH_CHECK_SERVICE";
+    /** See {@code Manifest#BIND_EXTERNAL_STORAGE_SERVICE} */
+    public static final String BIND_EXTERNAL_STORAGE_SERVICE = "android.permission"
+            + ".BIND_EXTERNAL_STORAGE_SERVICE";
+    /** See {@code Manifest#MANAGE_APPOPS} */
+    public static final String MANAGE_APPOPS = "android.permission.MANAGE_APPOPS";
+    /** See {@code Manifest#READ_CLIPBOARD_IN_BACKGROUND} */
+    public static final String READ_CLIPBOARD_IN_BACKGROUND = "android.permission"
+            + ".READ_CLIPBOARD_IN_BACKGROUND";
+    /** See {@code Manifest#MANAGE_ACCESSIBILITY} */
+    public static final String MANAGE_ACCESSIBILITY = "android.permission.MANAGE_ACCESSIBILITY";
+    /** See {@code Manifest#GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS} */
+    public static final String GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS =
+            "android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS";
+    /** See {@code Manifest#MARK_DEVICE_ORGANIZATION_OWNED} */
+    public static final String MARK_DEVICE_ORGANIZATION_OWNED = "android.permission"
+            + ".MARK_DEVICE_ORGANIZATION_OWNED";
+    /** See {@code Manifest#SMS_FINANCIAL_TRANSACTIONS} */
+    public static final String SMS_FINANCIAL_TRANSACTIONS =
+            "android.permission.SMS_FINANCIAL_TRANSACTIONS";
+    /** See {@code Manifest#USE_FULL_SCREEN_INTENT} */
+    public static final String USE_FULL_SCREEN_INTENT = "android.permission.USE_FULL_SCREEN_INTENT";
+    /** See {@code Manifest#SEND_DEVICE_CUSTOMIZATION_READY} */
+    public static final String SEND_DEVICE_CUSTOMIZATION_READY =
+            "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
+    /** See {@code Manifest#RECEIVE_DEVICE_CUSTOMIZATION_READY} */
+    public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY =
+            "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY";
+    /** See {@code Manifest#AMBIENT_WALLPAPER} */
+    public static final String AMBIENT_WALLPAPER = "android.permission.AMBIENT_WALLPAPER";
+    /** See {@code Manifest#MANAGE_SENSOR_PRIVACY} */
+    public static final String MANAGE_SENSOR_PRIVACY = "android.permission.MANAGE_SENSOR_PRIVACY";
+    /** See {@code Manifest#OBSERVE_SENSOR_PRIVACY} */
+    public static final String OBSERVE_SENSOR_PRIVACY = "android.permission.OBSERVE_SENSOR_PRIVACY";
+    /** See {@code Manifest#REVIEW_ACCESSIBILITY_SERVICES} */
+    public static final String REVIEW_ACCESSIBILITY_SERVICES =
+            "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
+    /** See {@code Manifest#SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON} */
+    public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON =
+            "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+    /** See {@code Manifest#ACCESS_SHARED_LIBRARIES} */
+    public static final String ACCESS_SHARED_LIBRARIES =
+            "android.permission.ACCESS_SHARED_LIBRARIES";
+    /** See {@code Manifest#LOG_COMPAT_CHANGE} */
+    public static final String LOG_COMPAT_CHANGE = "android.permission.LOG_COMPAT_CHANGE";
+    /** See {@code Manifest#READ_COMPAT_CHANGE_CONFIG} */
+    public static final String READ_COMPAT_CHANGE_CONFIG =
+            "android.permission.READ_COMPAT_CHANGE_CONFIG";
+    /** See {@code Manifest#OVERRIDE_COMPAT_CHANGE_CONFIG} */
+    public static final String OVERRIDE_COMPAT_CHANGE_CONFIG =
+            "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG";
+    /** See {@code Manifest#OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD} */
+    public static final String OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD =
+            "android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD";
+    /** See {@code Manifest#MONITOR_INPUT} */
+    public static final String MONITOR_INPUT = "android.permission.MONITOR_INPUT";
+    /** See {@code Manifest#ASSOCIATE_INPUT_DEVICE_TO_DISPLAY} */
+    public static final String ASSOCIATE_INPUT_DEVICE_TO_DISPLAY =
+            "android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY";
+    /** See {@code Manifest#QUERY_ALL_PACKAGES} */
+    public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+    /** See {@code Manifest#PEEK_DROPBOX_DATA} */
+    public static final String PEEK_DROPBOX_DATA = "android.permission.PEEK_DROPBOX_DATA";
+    /** See {@code Manifest#ACCESS_TV_TUNER} */
+    public static final String ACCESS_TV_TUNER = "android.permission.ACCESS_TV_TUNER";
+    /** See {@code Manifest#ACCESS_TV_DESCRAMBLER} */
+    public static final String ACCESS_TV_DESCRAMBLER = "android.permission.ACCESS_TV_DESCRAMBLER";
+    /** See {@code Manifest#ACCESS_TV_SHARED_FILTER} */
+    public static final String ACCESS_TV_SHARED_FILTER =
+            "android.permission.ACCESS_TV_SHARED_FILTER";
+    /** See {@code Manifest#ADD_TRUSTED_DISPLAY} */
+    public static final String ADD_TRUSTED_DISPLAY = "android.permission.ADD_TRUSTED_DISPLAY";
+    /** See {@code Manifest#ADD_ALWAYS_UNLOCKED_DISPLAY} */
+    public static final String ADD_ALWAYS_UNLOCKED_DISPLAY =
+            "android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY";
+    /** See {@code Manifest#ACCESS_LOCUS_ID_USAGE_STATS} */
+    public static final String ACCESS_LOCUS_ID_USAGE_STATS =
+            "android.permission.ACCESS_LOCUS_ID_USAGE_STATS";
+    /** See {@code Manifest#MANAGE_APP_HIBERNATION} */
+    public static final String MANAGE_APP_HIBERNATION = "android.permission.MANAGE_APP_HIBERNATION";
+    /** See {@code Manifest#RESET_APP_ERRORS} */
+    public static final String RESET_APP_ERRORS = "android.permission.RESET_APP_ERRORS";
+    /** See {@code Manifest#INPUT_CONSUMER} */
+    public static final String INPUT_CONSUMER = "android.permission.INPUT_CONSUMER";
+    /** See {@code Manifest#CONTROL_DEVICE_STATE} */
+    public static final String CONTROL_DEVICE_STATE = "android.permission.CONTROL_DEVICE_STATE";
+    /** See {@code Manifest#BIND_DISPLAY_HASHING_SERVICE} */
+    public static final String BIND_DISPLAY_HASHING_SERVICE =
+            "android.permission.BIND_DISPLAY_HASHING_SERVICE";
+    /** See {@code Manifest#MANAGE_TOAST_RATE_LIMITING} */
+    public static final String MANAGE_TOAST_RATE_LIMITING =
+            "android.permission.MANAGE_TOAST_RATE_LIMITING";
+    /** See {@code Manifest#MANAGE_GAME_MODE} */
+    public static final String MANAGE_GAME_MODE = "android.permission.MANAGE_GAME_MODE";
+    /** See {@code Manifest#SIGNAL_REBOOT_READINESS} */
+    public static final String SIGNAL_REBOOT_READINESS =
+            "android.permission.SIGNAL_REBOOT_READINESS";
+    /** See {@code Manifest#GET_PEOPLE_TILE_PREVIEW} */
+    public static final String GET_PEOPLE_TILE_PREVIEW =
+            "android.permission.GET_PEOPLE_TILE_PREVIEW";
+    /** See {@code Manifest#READ_PEOPLE_DATA} */
+    public static final String READ_PEOPLE_DATA = "android.permission.READ_PEOPLE_DATA";
+    /** See {@code Manifest#RENOUNCE_PERMISSIONS} */
+    public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
+    /** See {@code Manifest#READ_NEARBY_STREAMING_POLICY} */
+    public static final String READ_NEARBY_STREAMING_POLICY =
+            "android.permission.READ_NEARBY_STREAMING_POLICY";
+    /** See {@code Manifest#SET_CLIP_SOURCE} */
+    public static final String SET_CLIP_SOURCE = "android.permission.SET_CLIP_SOURCE";
+    /** See {@code Manifest#ACCESS_TUNED_INFO} */
+    public static final String ACCESS_TUNED_INFO = "android.permission.ACCESS_TUNED_INFO";
+    /** See {@code Manifest#UPDATE_PACKAGES_WITHOUT_USER_ACTION} */
+    public static final String UPDATE_PACKAGES_WITHOUT_USER_ACTION =
+            "android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION";
+    /** See {@code Manifest#CAPTURE_BLACKOUT_CONTENT} */
+    public static final String CAPTURE_BLACKOUT_CONTENT =
+            "android.permission.CAPTURE_BLACKOUT_CONTENT";
+    /** See {@code Manifest#READ_GLOBAL_APP_SEARCH_DATA} */
+    public static final String READ_GLOBAL_APP_SEARCH_DATA =
+            "android.permission.READ_GLOBAL_APP_SEARCH_DATA";
+    /** See {@code Manifest#CREATE_VIRTUAL_DEVICE} */
+    public static final String CREATE_VIRTUAL_DEVICE = "android.permission.CREATE_VIRTUAL_DEVICE";
+    /** See {@code Manifest#SEND_SAFETY_CENTER_UPDATE} */
+    public static final String SEND_SAFETY_CENTER_UPDATE =
+            "android.permission.SEND_SAFETY_CENTER_UPDATE";
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Tags.java b/common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/utils/Tags.java
similarity index 100%
rename from common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/Tags.java
rename to common/device-side/bedstead/nene/common/src/main/java/com/android/bedstead/nene/utils/Tags.java
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
index ecc04e3..a9721a7 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/TestApis.java
@@ -19,6 +19,7 @@
 import com.android.bedstead.nene.activities.Activities;
 import com.android.bedstead.nene.annotations.Experimental;
 import com.android.bedstead.nene.context.Context;
+import com.android.bedstead.nene.device.Device;
 import com.android.bedstead.nene.devicepolicy.DevicePolicy;
 import com.android.bedstead.nene.notifications.Notifications;
 import com.android.bedstead.nene.packages.Packages;
@@ -77,6 +78,11 @@
         return Notifications.sInstance;
     }
 
+    /** Access Test APIs related to the device. */
+    public static Device device() {
+        return Device.sInstance;
+    }
+
     /** @deprecated Use statically */
     @Deprecated()
     public TestApis() {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
index ee9927f..d7ca18a 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/appops/AppOps.java
@@ -18,7 +18,7 @@
 
 import static android.os.Build.VERSION_CODES.Q;
 
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_APP_OPS_MODES;
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_APP_OPS_MODES;
 
 import android.annotation.TargetApi;
 import android.app.AppOpsManager;
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java
new file mode 100644
index 0000000..b2530e8
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/device/Device.java
@@ -0,0 +1,68 @@
+/*
+ * 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.device;
+
+import android.os.RemoteException;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.bedstead.nene.exceptions.AdbException;
+import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.ShellCommand;
+
+/** Helper methods related to the device. */
+public final class Device {
+    public static final Device sInstance = new Device();
+    private static final UiDevice sDevice =
+            UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+    private Device() {
+
+    }
+
+    /**
+     * Turn the screen on.
+     */
+    public void wakeUp() {
+        try {
+            ShellCommand.builder("input keyevent")
+                    .addOperand("KEYCODE_WAKEUP")
+                    .validate(String::isEmpty)
+                    .execute();
+        } catch (AdbException e) {
+            throw new NeneException("Error waking up device", e);
+        }
+
+        Poll.forValue("isScreenOn", this::isScreenOn)
+                .toBeEqualTo(true)
+                .errorOnFail()
+                .await();
+    }
+
+    /**
+     * True if the screen is on.
+     */
+    public boolean isScreenOn() {
+        try {
+            return sDevice.isScreenOn();
+        } catch (RemoteException e) {
+            throw new NeneException("Error getting isScreenOn", e);
+        }
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
index ae3d100..908c419 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DeviceOwner.java
@@ -16,10 +16,17 @@
 
 package com.android.bedstead.nene.devicepolicy;
 
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
+import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils.ACTION_DISABLE_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Build;
 
 import com.android.bedstead.nene.TestApis;
@@ -28,16 +35,24 @@
 import com.android.bedstead.nene.packages.Package;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
 import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.bedstead.nene.utils.ShellCommandUtils;
 import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
  * A reference to a Device Owner.
  */
 public final class DeviceOwner extends DevicePolicyController {
+
+    private static final String TEST_APP_APP_COMPONENT_FACTORY =
+            "com.android.bedstead.testapp.TestAppAppComponentFactory";
+
     DeviceOwner(UserReference user,
             Package pkg,
             ComponentName componentName) {
@@ -57,7 +72,8 @@
 
     @Override
     public void remove() {
-        if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
+        if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)
+                || TestApis.packages().instrumented().isInstantApp()) {
             removePreS();
             return;
         }
@@ -69,7 +85,18 @@
         try (PermissionContext p =
                      TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
             devicePolicyManager.forceRemoveActiveAdmin(mComponentName, mUser.id());
+        } catch (SecurityException e) {
+            if (e.getMessage().contains("Attempt to remove non-test admin")
+                    && TEST_APP_APP_COMPONENT_FACTORY.equals(mPackage.appComponentFactory())) {
+                removeTestApp();
+            } else {
+                throw e;
+            }
         }
+
+        Poll.forValue("Device Owner", () -> TestApis.devicePolicy().getDeviceOwner())
+                .toBeNull()
+                .errorOnFail().await();
     }
 
     private void removePreS() {
@@ -79,7 +106,39 @@
                     .validate(ShellCommandUtils::startsWithSuccess)
                     .execute();
         } catch (AdbException e) {
-            throw new NeneException("Error removing device owner " + this, e);
+            if (mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+                    && user().parent() == null) {
+                // We can't see why it failed so we'll try the test app version
+                removeTestApp();
+            } else {
+                throw new NeneException("Error removing device owner " + this, e);
+            }
+        }
+    }
+
+    private void removeTestApp() {
+        // Special case for removing TestApp DPCs - this works even when not testOnly
+        Intent intent = new Intent(ACTION_DISABLE_SELF);
+        intent.setComponent(new ComponentName(pkg().packageName(),
+                "com.android.bedstead.testapp.TestAppBroadcastController"));
+
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            // If the profile isn't ready then the broadcast won't be sent and the profile owner
+            // will not be removed. So we can retry until the broadcast has been dealt with.
+            Retry.logic(() -> {
+                BlockingBroadcastReceiver b = new BlockingBroadcastReceiver(
+                        TestApis.context().instrumentedContext());
+
+                TestApis.context().androidContextAsUser(mUser).sendOrderedBroadcast(
+                        intent, /* receiverPermission= */ null, b, /* scheduler= */
+                        null, /* initialCode= */
+                        Activity.RESULT_CANCELED, /* initialData= */ null, /* initialExtras= */
+                        null);
+
+                b.awaitForBroadcastOrFail(Duration.ofSeconds(30).toMillis());
+                assertThat(b.getResultCode()).isEqualTo(Activity.RESULT_OK);
+            }).timeout(Duration.ofMinutes(5)).runAndWrapException();
         }
     }
 
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
index 2d8d21d..7a1eaf9 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/DevicePolicy.java
@@ -42,11 +42,14 @@
 import com.android.bedstead.nene.packages.Package;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
 import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.bedstead.nene.utils.ShellCommandUtils;
 import com.android.bedstead.nene.utils.Versions;
 import com.android.compatibility.common.util.PollingCheck;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
@@ -64,8 +67,6 @@
 
     private static final String LOG_TAG = "DevicePolicy";
 
-    private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete";
-
     private final AdbDevicePolicyParser mParser;
 
     private DeviceOwner mCachedDeviceOwner;
@@ -91,44 +92,40 @@
         // TODO(b/187925230): If it fails, we check for terminal failure states - and if not
         //  we retry because if the profile owner was recently removed, it can take some time
         //  to be allowed to set it again
-        retryIfNotTerminal(
-                () -> command.executeOrThrowNeneException("Could not set profile owner for user "
-                        + user + " component " + profileOwnerComponent),
-                () -> checkForTerminalProfileOwnerFailures(user, profileOwnerComponent),
-                NeneException.class);
+        try {
+            Retry.logic(command::execute)
+                    .terminalException((ex) -> {
+                        if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
+                            return false; // Just retry on old versions as we don't have stderr
+                        }
+                        if (ex instanceof AdbException) {
+                            if (((AdbException) ex).error().contains("is being removed")) {
+                                return false;
+                            }
+                        }
+
+                        // Assume all other errors are terminal
+                        return true;
+                    })
+                    .timeout(Duration.ofMinutes(5))
+                    .run();
+        } catch (Throwable e) {
+            throw new NeneException("Could not set profile owner for user "
+                    + user + " component " + profileOwnerComponent, e);
+        }
+
+        Poll.forValue("Profile Owner", () -> TestApis.devicePolicy().getProfileOwner(user))
+                .toNotBeNull()
+                .errorOnFail()
+                .await();
+
         return new ProfileOwner(user,
                 TestApis.packages().find(
                         profileOwnerComponent.getPackageName()), profileOwnerComponent);
     }
 
-    private void checkForTerminalProfileOwnerFailures(
-            UserReference user, ComponentName profileOwnerComponent) {
-        ProfileOwner profileOwner = getProfileOwner(user);
-        if (profileOwner != null) {
-            // TODO(scottjonathan): Should we actually fail here if the component name is the
-            //  same?
-
-            throw new NeneException(
-                    "Could not set profile owner for user " + user
-                            + " as a profile owner is already set: " + profileOwner);
-        }
-
-        Package pkg = TestApis.packages().find(
-                profileOwnerComponent.getPackageName());
-        if (!TestApis.packages().installedForUser(user).contains(pkg)) {
-            throw new NeneException(
-                    "Could not set profile owner for user " + user
-                            + " as the package " + pkg + " is not installed");
-        }
-
-        if (!componentCanBeSetAsDeviceAdmin(profileOwnerComponent, user)) {
-            throw new NeneException("Could not set profile owner for user "
-                    + user + " as component " + profileOwnerComponent + " is not valid");
-        }
-    }
-
     /**
-     * Get the profile owner for the instrumented user..
+     * Get the profile owner for the instrumented user.
      */
     public ProfileOwner getProfileOwner() {
         return getProfileOwner(TestApis.users().instrumented());
@@ -162,25 +159,27 @@
                         .getSystemService(DevicePolicyManager.class);
         UserReference user = TestApis.users().system();
 
-        boolean dpmUserSetupComplete = getUserSetupComplete(user);
+        boolean dpmUserSetupComplete = user.getSetupComplete();
         Boolean currentUserSetupComplete = null;
 
         try {
-            setUserSetupComplete(user, false);
+            user.setSetupComplete(false);
 
             try (PermissionContext p =
                          TestApis.permissions().withPermission(
                                  MANAGE_PROFILE_AND_DEVICE_OWNERS, MANAGE_DEVICE_ADMINS,
                                  INTERACT_ACROSS_USERS_FULL, INTERACT_ACROSS_USERS, CREATE_USERS)) {
-                devicePolicyManager.setActiveAdmin(deviceOwnerComponent,
-                        /* refreshing= */ true, user.id());
 
                 // TODO(b/187925230): If it fails, we check for terminal failure states - and if not
                 //  we retry because if the DO/PO was recently removed, it can take some time
                 //  to be allowed to set it again
                 retryIfNotTerminal(
-                        () -> setDeviceOwnerOnly(devicePolicyManager,
-                                deviceOwnerComponent, "Nene", user.id()),
+                        () -> {
+                            devicePolicyManager.setActiveAdmin(deviceOwnerComponent,
+                                    /* refreshing= */ true, user.id());
+                            setDeviceOwnerOnly(devicePolicyManager,
+                                    deviceOwnerComponent, "Nene", user.id());
+                        },
                         () -> checkForTerminalDeviceOwnerFailures(
                                 user, deviceOwnerComponent, /* allowAdditionalUsers= */ true),
                         NeneException.class);
@@ -188,15 +187,20 @@
                 throw new NeneException("Error setting device owner", e);
             }
         } finally {
-            setUserSetupComplete(user, dpmUserSetupComplete);
+            user.setSetupComplete(dpmUserSetupComplete);
             if (currentUserSetupComplete != null) {
-                setUserSetupComplete(TestApis.users().current(), currentUserSetupComplete);
+                TestApis.users().current().setSetupComplete(currentUserSetupComplete);
             }
         }
 
         Package deviceOwnerPackage = TestApis.packages().find(
                 deviceOwnerComponent.getPackageName());
 
+        Poll.forValue("Device Owner", () -> TestApis.devicePolicy().getDeviceOwner())
+                .toNotBeNull()
+                .errorOnFail()
+                .await();
+
         return new DeviceOwner(user, deviceOwnerPackage, deviceOwnerComponent);
     }
 
@@ -268,26 +272,6 @@
         }
     }
 
-
-    private void setUserSetupComplete(UserReference user, boolean complete) {
-        DevicePolicyManager devicePolicyManager =
-                TestApis.context().androidContextAsUser(user)
-                        .getSystemService(DevicePolicyManager.class);
-        TestApis.settings().secure().putInt(user, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0);
-        try (PermissionContext p =
-                     TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
-            devicePolicyManager.forceUpdateUserSetupComplete(user.id());
-        }
-    }
-
-    private boolean getUserSetupComplete(UserReference user) {
-        try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
-            return
-                    TestApis.settings().secure()
-                            .getInt(user, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1;
-        }
-    }
-
     private DeviceOwner setDeviceOwnerPreS(ComponentName deviceOwnerComponent) {
         UserReference user = TestApis.users().system();
 
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
index e475d25..8c21b65 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/devicepolicy/ProfileOwner.java
@@ -16,10 +16,17 @@
 
 package com.android.bedstead.nene.devicepolicy;
 
-import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 
+import static com.android.bedstead.nene.permissions.Permissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
+import static com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils.ACTION_DISABLE_SELF;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Activity;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.os.Build;
 
 import com.android.bedstead.nene.TestApis;
@@ -28,10 +35,14 @@
 import com.android.bedstead.nene.packages.Package;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.utils.Poll;
+import com.android.bedstead.nene.utils.Retry;
 import com.android.bedstead.nene.utils.ShellCommand;
 import com.android.bedstead.nene.utils.ShellCommandUtils;
 import com.android.bedstead.nene.utils.Versions;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
+import java.time.Duration;
 import java.util.Objects;
 
 /**
@@ -39,6 +50,9 @@
  */
 public final class ProfileOwner extends DevicePolicyController {
 
+    private static final String TEST_APP_APP_COMPONENT_FACTORY =
+            "com.android.bedstead.testapp.TestAppAppComponentFactory";
+
     ProfileOwner(UserReference user,
             Package pkg,
             ComponentName componentName) {
@@ -60,7 +74,20 @@
         try (PermissionContext p =
                      TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
             devicePolicyManager.forceRemoveActiveAdmin(mComponentName, mUser.id());
+        } catch (SecurityException e) {
+            if (e.getMessage().contains("Attempt to remove non-test admin")
+                    && mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+                    && user().parent() == null) {
+                removeTestApp();
+            } else {
+                throw e;
+            }
         }
+
+        Poll.forValue("Profile Owner",
+                () -> TestApis.devicePolicy().getProfileOwner(mUser))
+                .toBeNull()
+                .errorOnFail().await();
     }
 
     private void removePreS() {
@@ -70,7 +97,40 @@
                     .validate(ShellCommandUtils::startsWithSuccess)
                     .execute();
         } catch (AdbException e) {
-            throw new NeneException("Error removing profile owner " + this, e);
+            if (mPackage.appComponentFactory().equals(TEST_APP_APP_COMPONENT_FACTORY)
+                    && user().parent() == null) {
+                // We can't see why it failed so we'll try the test app version
+                removeTestApp();
+            } else {
+                throw new NeneException("Error removing profile owner " + this, e);
+            }
+        }
+    }
+
+    private void removeTestApp() {
+        // Special case for removing TestApp DPCs - this works even when not testOnly
+        // but not on profiles
+        Intent intent = new Intent(ACTION_DISABLE_SELF);
+        intent.setComponent(new ComponentName(pkg().packageName(),
+                "com.android.bedstead.testapp.TestAppBroadcastController"));
+
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            // If the profile isn't ready then the broadcast won't be sent and the profile owner
+            // will not be removed. So we can retry until the broadcast has been dealt with.
+            Retry.logic(() -> {
+                BlockingBroadcastReceiver b = new BlockingBroadcastReceiver(
+                        TestApis.context().instrumentedContext());
+
+                TestApis.context().androidContextAsUser(mUser).sendOrderedBroadcast(
+                        intent, /* receiverPermission= */ null, b, /* scheduler= */
+                        null, /* initialCode= */
+                        Activity.RESULT_CANCELED, /* initialData= */ null, /* initialExtras= */
+                        null);
+
+                b.awaitForBroadcastOrFail(Duration.ofSeconds(30).toMillis());
+                assertThat(b.getResultCode()).isEqualTo(Activity.RESULT_OK);
+            }).timeout(Duration.ofMinutes(5)).runAndWrapException();
         }
     }
 
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
deleted file mode 100644
index ca3ef6d..0000000
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/exceptions/AdbException.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.exceptions;
-
-import androidx.annotation.Nullable;
-
-/**
- * An exception that gets thrown when interacting with Adb.
- */
-public class AdbException extends Exception {
-
-    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.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) {
-        super(message, cause);
-        if (command == null) {
-            throw new NullPointerException();
-        }
-        this.mCommand = command;
-        this.mOutput = output;
-        this.mErr = null;
-    }
-
-    public String command() {
-        return mCommand;
-    }
-
-    public String output() {
-        return mOutput;
-    }
-
-    public String error() {
-        return mErr;
-    }
-
-    @Override
-    public String toString() {
-        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/Package.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
index 3b76374..fc2027f 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Package.java
@@ -26,11 +26,13 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
 import static android.content.pm.PermissionInfo.PROTECTION_FLAG_DEVELOPMENT;
+import static android.os.Build.VERSION_CODES.P;
 import static android.os.Build.VERSION_CODES.S;
 import static android.os.Process.myUid;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -781,18 +783,18 @@
         return Packages.parseDumpsys().mPackages.containsKey(mPackageName);
     }
 
+    /** Get the targetSdkVersion for the package. */
+    @Experimental
+    public int targetSdkVersion() {
+        return applicationInfoFromAnyUserOrError(/* flags= */ 0).targetSdkVersion;
+    }
+
     /**
      * {@code true} if the package is installed in the device's system image.
      */
     @Experimental
     public boolean hasSystemFlag() {
-        ApplicationInfo appInfo = applicationInfoFromAnyUser(/* flags= */ 0);
-
-        if (appInfo == null) {
-            throw new NeneException("Package not installed: " + this);
-        }
-
-        return (appInfo.flags & FLAG_SYSTEM) > 0;
+        return (applicationInfoFromAnyUserOrError(/* flags= */ 0).flags & FLAG_SYSTEM) > 0;
     }
 
     @Experimental
@@ -800,6 +802,22 @@
         return sPackageManager.isInstantApp(mPackageName);
     }
 
+    /** Get the AppComponentFactory for the package. */
+    @Experimental
+    @Nullable
+    @TargetApi(P)
+    public String appComponentFactory() {
+        return applicationInfoFromAnyUserOrError(/* flags= */ 0).appComponentFactory;
+    }
+
+    private ApplicationInfo applicationInfoFromAnyUserOrError(int flags) {
+        ApplicationInfo appInfo = applicationInfoFromAnyUser(flags);
+        if (appInfo == null) {
+            throw new NeneException("Package not installed: " + this);
+        }
+        return appInfo;
+    }
+
     /**
      * Gets the shared user id of the package.
      */
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
index fa797bb..b9c92ff 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/permissions/Permissions.java
@@ -250,7 +250,7 @@
 
         Set<String> adoptedShellPermissions = new HashSet<>();
         for (String permission : grantedPermissions) {
-            checkCanGrantOnAllSupportedVersions(permission, sUser);
+            checkCanGrantOnAllSupportedVersions(permission);
 
             Log.d(LOG_TAG, "Trying to grant " + permission);
             if (sInstrumentedPackage.hasPermission(sUser, permission)) {
@@ -269,7 +269,7 @@
                 removePermissionContextsUntilCanApply();
 
                 throwPermissionException("PermissionContext requires granting "
-                        + permission + " but cannot.", permission, sUser);
+                        + permission + " but cannot.", permission);
             }
         }
 
@@ -284,7 +284,7 @@
             } else { // We can't deny a permission to ourselves
                 removePermissionContextsUntilCanApply();
                 throwPermissionException("PermissionContext requires denying "
-                        + permission + " but cannot.", permission, sUser);
+                        + permission + " but cannot.", permission);
             }
         }
 
@@ -296,7 +296,7 @@
     }
 
     private void checkCanGrantOnAllSupportedVersions(
-            String permission, UserReference user) {
+            String permission) {
         if (sCheckedGrantPermissions.contains(permission)) {
             return;
         }
@@ -310,7 +310,7 @@
                             + "possible add it to"
                             + "com.android.bedstead.nene.permissions"
                             + ".Permissions#EXEMPT_SHELL_PERMISSIONS",
-                    permission, user);
+                    permission);
         }
 
         sCheckedGrantPermissions.add(permission);
@@ -325,8 +325,11 @@
         sCheckedDenyPermissions.add(permission);
     }
 
-    private void throwPermissionException(
-            String message, String permission, UserReference user) {
+    /**
+     * Throw an exception including permission contextual information.
+     */
+    public void throwPermissionException(
+            String message, String permission) {
         String protectionLevel = "Permission not found";
         try {
             protectionLevel = Integer.toString(sPackageManager.getPermissionInfo(
@@ -335,7 +338,7 @@
             Log.e(LOG_TAG, "Permission not found", e);
         }
 
-        throw new NeneException(message + "\n\nRunning On User: " + user
+        throw new NeneException(message + "\n\nRunning On User: " + sUser
                 + "\nPermission: " + permission
                 + "\nPermission protection level: " + protectionLevel
                 + "\nPermission state: " + sContext.checkSelfPermission(permission)
@@ -354,6 +357,13 @@
     }
 
     /**
+     * Returns all of the permissions which can be adopted.
+     */
+    public Set<String> adoptablePermissions() {
+        return mShellPermissions;
+    }
+
+    /**
      * Returns all of the permissions which are currently able to be used.
      */
     public Set<String> usablePermissions() {
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
index b33b402..b1c5891 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/settings/SecureSettings.java
@@ -175,6 +175,138 @@
     }
 
     /**
+     * See {@link Settings.Secure#putString(ContentResolver, String, String)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public void putString(ContentResolver contentResolver, String key, String value) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p = sTestApis.permissions().withPermission(
+                INTERACT_ACROSS_USERS_FULL, WRITE_SECURE_SETTINGS)) {
+            Settings.Secure.putString(contentResolver, key, value);
+        }
+    }
+
+    /**
+     * Put String to secure settings for the given {@link UserReference}.
+     *
+     * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+     * and above.
+     *
+     * <p>See {@link #putString(ContentResolver, String, String)}
+     */
+    @SuppressLint("NewApi")
+    public void putString(UserReference user, String key, String value) {
+        if (user.equals(sTestApis.users().instrumented())) {
+            putString(key, value);
+            return;
+        }
+
+        putString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key, value);
+    }
+
+    /**
+     * Put String to secure settings for the instrumented user.
+     *
+     * <p>See {@link #putString(ContentResolver, String, String)}
+     */
+    public void putString(String key, String value) {
+        try (PermissionContext p = sTestApis.permissions().withPermission(
+                WRITE_SECURE_SETTINGS)) {
+            Settings.Secure.putString(
+                    sTestApis.context().instrumentedContext().getContentResolver(), key, value);
+        }
+    }
+
+    /**
+     * See {@link Settings.Secure#getString(ContentResolver, String)}
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public String getString(ContentResolver contentResolver, String key) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p =
+                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            return getStringInner(contentResolver, key);
+        }
+    }
+
+    /**
+     * See {@link Settings.Secure#getString(ContentResolver, String)}.
+     *
+     * <p>If the value is null, the {@code defaultValue} will be returned.
+     */
+    @RequiresApi(Build.VERSION_CODES.S)
+    public String getString(ContentResolver contentResolver, String key, String defaultValue) {
+        Versions.requireMinimumVersion(Build.VERSION_CODES.S);
+        try (PermissionContext p =
+                     sTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            return getStringInner(contentResolver, key, defaultValue);
+        }
+    }
+
+    private String getStringInner(ContentResolver contentResolver, String key) {
+        return Settings.Secure.getString(contentResolver, key);
+    }
+
+    private String getStringInner(
+            ContentResolver contentResolver, String key, String defaultValue) {
+        String s = getStringInner(contentResolver, key);
+        return s == null ? defaultValue : s;
+    }
+
+    /**
+     * Get String from secure settings for the given {@link UserReference}.
+     *
+     * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+     * and above.
+     *
+     * <p>See {@link #getString(ContentResolver, String)}
+     */
+    @SuppressLint("NewApi")
+    public String getString(UserReference user, String key) {
+        if (user.equals(sTestApis.users().instrumented())) {
+            return getString(key);
+        }
+        return getString(sTestApis.context().androidContextAsUser(user).getContentResolver(), key);
+    }
+
+    /**
+     * Get String from secure settings for the given {@link UserReference}, or the default value.
+     *
+     * <p>If the user is not the instrumented user, this will only succeed when running on Android S
+     * and above.
+     *
+     * <p>See {@link #getString(ContentResolver, String, String)}
+     */
+    @SuppressLint("NewApi")
+    public String getString(UserReference user, String key, String defaultValue) {
+        if (user.equals(sTestApis.users().instrumented())) {
+            return getString(key, defaultValue);
+        }
+        return getString(
+                sTestApis.context().androidContextAsUser(user).getContentResolver(),
+                key, defaultValue);
+    }
+
+    /**
+     * Get String from secure settings for the instrumented user.
+     *
+     * <p>See {@link #getString(ContentResolver, String)}
+     */
+    public String getString(String key) {
+        return getStringInner(sTestApis.context().instrumentedContext().getContentResolver(), key);
+    }
+
+    /**
+     * Get String from secure settings for the instrumented user, or the default value.
+     *
+     * <p>See {@link #getString(ContentResolver, String)}
+     */
+    public String getString(String key, String defaultValue) {
+        return getStringInner(
+                sTestApis.context().instrumentedContext().getContentResolver(), key, defaultValue);
+    }
+
+    /**
      * Reset all secure settings set by this package.
      *
      * See {@link Settings.Secure#resetToDefaults(ContentResolver, String)}.
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 31e2685..c10efed 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
@@ -25,6 +25,7 @@
 import androidx.annotation.CheckResult;
 import androidx.annotation.Nullable;
 
+import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.nene.utils.ShellCommand;
@@ -138,7 +139,7 @@
                     commandBuilder.validate(ShellCommandUtils::startsWithSuccess)
                             .executeAndParseOutput(
                                     (output) -> Integer.parseInt(output.split("id ")[1].trim()));
-            return new UserReference(userId);
+            return TestApis.users().find(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 aa9343f..4ca2fce 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
@@ -22,8 +22,11 @@
 import static android.os.Build.VERSION_CODES.R;
 import static android.os.Build.VERSION_CODES.S;
 
+import static com.android.bedstead.nene.permissions.CommonPermissions.MANAGE_PROFILE_AND_DEVICE_OWNERS;
 import static com.android.bedstead.nene.users.Users.users;
 
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
 import android.content.Intent;
 import android.content.pm.UserInfo;
 import android.os.UserHandle;
@@ -33,6 +36,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.annotations.Experimental;
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.exceptions.NeneException;
 import com.android.bedstead.nene.permissions.PermissionContext;
@@ -60,6 +64,8 @@
 
     private static final String LOG_TAG = "UserReference";
 
+    private static final String USER_SETUP_COMPLETE_KEY = "user_setup_complete";
+
     private final int mId;
 
     private final UserManager mUserManager;
@@ -70,6 +76,7 @@
     private Boolean mIsPrimary;
     private boolean mParentCached = false;
     private UserReference mParent;
+    private @Nullable String mPassword;
 
     UserReference(int id) {
         mId = id;
@@ -371,6 +378,108 @@
         return users().anyMatch(ui -> ui.id == id());
     }
 
+    /**
+     * Sets the value of {@code user_setup_complete} in secure settings to {@code complete}.
+     */
+    @Experimental
+    public void setSetupComplete(boolean complete) {
+        if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
+            return;
+        }
+        DevicePolicyManager devicePolicyManager =
+                TestApis.context().androidContextAsUser(this)
+                        .getSystemService(DevicePolicyManager.class);
+        TestApis.settings().secure().putInt(
+                /* user= */ this, USER_SETUP_COMPLETE_KEY, complete ? 1 : 0);
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)) {
+            devicePolicyManager.forceUpdateUserSetupComplete(id());
+        }
+    }
+
+    /**
+     * Gets the value of {@code user_setup_complete} from secure settings.
+     */
+    @Experimental
+    public boolean getSetupComplete() {
+        try (PermissionContext p = TestApis.permissions().withPermission(CREATE_USERS)) {
+            return TestApis.settings().secure()
+                    .getInt(/*user= */ this, USER_SETUP_COMPLETE_KEY, /* def= */ 0) == 1;
+        }
+    }
+
+    /**
+     * True if the user has a password.
+     */
+    public boolean hasPassword() {
+        return TestApis.context().androidContextAsUser(this)
+                .getSystemService(KeyguardManager.class).isDeviceSecure();
+    }
+
+    /**
+     * Set a password for the user.
+     */
+    public void setPassword(String password) {
+        try {
+            ShellCommand.builder("cmd lock_settings")
+                    .addOperand("set-password")
+                    .addOperand(password)
+                    .addOption("--user", mId)
+                    .validate(s -> s.startsWith("Password set to "))
+                    .execute();
+        } catch (AdbException e) {
+            throw new NeneException("Error setting password", e);
+        }
+        mPassword = password;
+    }
+
+    /**
+     * Clear the password for the user, using the password that was last set using Nene.
+     */
+    public void clearPassword() {
+        if (mPassword == null) {
+            throw new NeneException(
+                    "clearPassword() can only be called when setPassword was used to set the"
+                            + " password");
+        }
+        clearPassword(mPassword);
+    }
+
+    /**
+     * Clear the password for the user.
+     */
+    public void clearPassword(String password) {
+
+        try {
+            ShellCommand.builder("cmd lock_settings")
+                    .addOperand("clear")
+                    .addOption("--old", password)
+                    .addOption("--user", mId)
+                    .validate(s -> s.startsWith("Lock credential cleared"))
+                    .execute();
+        } catch (AdbException e) {
+            if (e.output().contains("user has no password")) {
+                // No password anyway, fine
+                mPassword = null;
+                return;
+            }
+            throw new NeneException("Error clearing password", e);
+        }
+
+        mPassword = null;
+    }
+
+    /**
+     * Returns the password for this user if that password was set using Nene.
+     *
+     *
+     * <p>If there is no password, or the password was not set using Nene, then this will
+     * return {@code null}.
+     */
+    public @Nullable String password() {
+        return mPassword;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (!(obj instanceof UserReference)) {
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 8813a46..cf0891a 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
@@ -58,6 +58,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -73,6 +74,7 @@
     private final AdbUserParser mParser;
     private static final UserManager sUserManager =
             TestApis.context().instrumentedContext().getSystemService(UserManager.class);
+    private Map<Integer, UserReference> mUsers = new ConcurrentHashMap<>();
 
     public static final Users sInstance = new Users();
 
@@ -89,7 +91,7 @@
         }
 
         return users().map(
-                ui -> new UserReference(ui.id)
+                ui -> find(ui.id)
         ).collect(Collectors.toSet());
     }
 
@@ -152,12 +154,15 @@
 
     /** Get a {@link UserReference} by {@code id}. */
     public UserReference find(int id) {
-        return new UserReference(id);
+        if (!mUsers.containsKey(id)) {
+            mUsers.put(id, new UserReference(id));
+        }
+        return mUsers.get(id);
     }
 
     /** Get a {@link UserReference} by {@code userHandle}. */
     public UserReference find(UserHandle userHandle) {
-        return new UserReference(userHandle.getIdentifier());
+        return find(userHandle.getIdentifier());
     }
 
     /** Get all supported {@link UserType}s. */
@@ -328,7 +333,7 @@
             id++;
         }
 
-        return new UserReference(id);
+        return find(id);
     }
 
     private void fillCache() {
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 7818bcc..4c686d6 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
@@ -59,7 +59,6 @@
     }
 
     public static final class Builder {
-        private String mLinuxUser;
         private final StringBuilder commandBuilder;
         @Nullable
         private byte[] mStdInBytes = null;
@@ -129,9 +128,6 @@
          * Build the full command including all options and operands.
          */
         public String build() {
-            if (mLinuxUser != null) {
-                return "su " + mLinuxUser + " " + commandBuilder.toString();
-            }
             return commandBuilder.toString();
         }
 
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java
new file mode 100644
index 0000000..a660beb
--- /dev/null
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/device/DeviceTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.device;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.nene.TestApis;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BedsteadJUnit4.class)
+public final class DeviceTest {
+
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @Test
+    public void wakeUp_screenTurnsOn() {
+        TestApis.device().wakeUp();
+
+        assertThat(TestApis.device().isScreenOn()).isTrue();
+    }
+}
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
index 91932c1..652faff 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/DeviceOwnerTest.java
@@ -23,8 +23,12 @@
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.remotedpc.RemoteDpc;
+import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppInstance;
+import com.android.bedstead.testapp.TestAppProvider;
 
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -40,6 +44,15 @@
     public static DeviceState sDeviceState = new DeviceState();
 
     private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
+    private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+    private static final TestApp sNonTestOnlyDpc = sTestAppProvider.query()
+            .whereIsDeviceAdmin().isTrue()
+            .whereTestOnly().isFalse()
+            .get();
+    private static final ComponentName NON_TEST_ONLY_DPC_COMPONENT_NAME = new ComponentName(
+            sNonTestOnlyDpc.packageName(),
+            "com.android.bedstead.testapp.DeviceAdminTestApp.DeviceAdminReceiver"
+    );
 
     private DeviceOwner mDeviceOwner;
 
@@ -69,4 +82,18 @@
 
         assertThat(TestApis.devicePolicy().getDeviceOwner()).isNull();
     }
+
+
+    @Test
+    @EnsureHasNoDpc
+    public void remove_nonTestOnlyDpc_removesDeviceOwner() {
+        try (TestAppInstance dpc = sNonTestOnlyDpc.install()) {
+            DeviceOwner deviceOwner = TestApis.devicePolicy()
+                    .setDeviceOwner(NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+            deviceOwner.remove();
+
+            assertThat(TestApis.devicePolicy().getDeviceOwner()).isNull();
+        }
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
index 0bb85a0..39e25d3 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/devicepolicy/ProfileOwnerTest.java
@@ -22,11 +22,17 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.users.UserReference;
 import com.android.bedstead.remotedpc.RemoteDpc;
 import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppInstance;
+import com.android.bedstead.testapp.TestAppProvider;
 
 import org.junit.Before;
 import org.junit.ClassRule;
@@ -38,9 +44,17 @@
 public class ProfileOwnerTest {
 
     private static final ComponentName DPC_COMPONENT_NAME = RemoteDpc.DPC_COMPONENT_NAME;
+    private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+    private static final TestApp sNonTestOnlyDpc = sTestAppProvider.query()
+            .whereIsDeviceAdmin().isTrue()
+            .whereTestOnly().isFalse()
+            .get();
+    private static final ComponentName NON_TEST_ONLY_DPC_COMPONENT_NAME = new ComponentName(
+            sNonTestOnlyDpc.packageName(),
+            "com.android.bedstead.testapp.DeviceAdminTestApp.DeviceAdminReceiver"
+    );
+
     private static UserReference sProfile;
-    private static TestApp sTestApp;
-    private static DevicePolicyController sProfileOwner;
 
     @ClassRule @Rule
     public static final DeviceState sDeviceState = new DeviceState();
@@ -48,36 +62,71 @@
     @Before
     public void setUp() {
         sProfile = TestApis.users().instrumented();
-        sTestApp = sDeviceState.dpc().testApp();
-        sProfileOwner = sDeviceState.profileOwner().devicePolicyController();
     }
 
     @Test
     @EnsureHasProfileOwner
     public void user_returnsUser() {
-        assertThat(sProfileOwner.user()).isEqualTo(sProfile);
+        assertThat(sDeviceState.profileOwner().devicePolicyController().user()).isEqualTo(sProfile);
     }
 
     @Test
     @EnsureHasProfileOwner
     public void pkg_returnsPackage() {
-        assertThat(sProfileOwner.pkg()).isEqualTo(sTestApp.pkg());
+        assertThat(sDeviceState.profileOwner().devicePolicyController().pkg()).isNotNull();
     }
 
     @Test
     @EnsureHasProfileOwner
     public void componentName_returnsComponentName() {
-        assertThat(sProfileOwner.componentName()).isEqualTo(DPC_COMPONENT_NAME);
+        assertThat(sDeviceState.profileOwner().devicePolicyController().componentName())
+                .isEqualTo(DPC_COMPONENT_NAME);
     }
 
     @Test
     @EnsureHasProfileOwner
     public void remove_removesProfileOwner() {
-        sProfileOwner.remove();
+        sDeviceState.profileOwner().devicePolicyController().remove();
         try {
             assertThat(TestApis.devicePolicy().getProfileOwner(sProfile)).isNull();
         } finally {
-            sProfileOwner = TestApis.devicePolicy().setProfileOwner(sProfile, DPC_COMPONENT_NAME);
+            TestApis.devicePolicy().setProfileOwner(sProfile, DPC_COMPONENT_NAME);
         }
     }
+
+    @Test
+    @EnsureHasNoDpc
+    public void remove_nonTestOnlyDpc_removesProfileOwner() {
+        try (TestAppInstance dpc = sNonTestOnlyDpc.install()) {
+            ProfileOwner profileOwner = TestApis.devicePolicy().setProfileOwner(
+                    TestApis.users().instrumented(), NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+            profileOwner.remove();
+
+            assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
+        }
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireRunNotOnSecondaryUser
+    public void remove_onOtherUser_removesProfileOwner() {
+        try (TestAppInstance dpc = sNonTestOnlyDpc.install(sDeviceState.secondaryUser())) {
+            ProfileOwner profileOwner = TestApis.devicePolicy().setProfileOwner(
+                    sDeviceState.secondaryUser(), NON_TEST_ONLY_DPC_COMPONENT_NAME);
+
+            profileOwner.remove();
+
+            assertThat(TestApis.devicePolicy().getProfileOwner(sDeviceState.secondaryUser()))
+                    .isNull();
+        }
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    public void remove_onWorkProfile_testDpc_removesProfileOwner() {
+        TestApis.devicePolicy().getProfileOwner().remove();
+
+        assertThat(TestApis.devicePolicy().getProfileOwner()).isNull();
+    }
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
index 1d662fe..0b09802 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/notifications/NotificationsTest.java
@@ -16,6 +16,8 @@
 
 package com.android.bedstead.nene.notifications;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+
 import static com.android.bedstead.nene.notifications.NotificationListenerQuerySubject.assertThat;
 import static com.android.bedstead.nene.notifications.Notifications.LISTENER_COMPONENT;
 
@@ -32,6 +34,7 @@
 import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.NeneException;
+import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.bedstead.nene.users.UserReference;
 
 import org.junit.ClassRule;
@@ -101,16 +104,18 @@
     }
 
     private void createNotification() {
-        String channelId = "notifications";
-        sNotificationManager.createNotificationChannel(new NotificationChannel(channelId,
-                "notifications",
-                NotificationChannel.USER_LOCKED_IMPORTANCE));
+        try (PermissionContext p = TestApis.permissions().withPermission(POST_NOTIFICATIONS)) {
+            String channelId = "notifications";
+            sNotificationManager.createNotificationChannel(new NotificationChannel(channelId,
+                    "notifications",
+                    NotificationChannel.USER_LOCKED_IMPORTANCE));
 
-        Notification.Builder notificationBuilder =
-                new Notification.Builder(TestApis.context().instrumentedContext(), channelId)
-                .setSmallIcon(R.drawable.sym_def_app_icon);
+            Notification.Builder notificationBuilder =
+                    new Notification.Builder(TestApis.context().instrumentedContext(), channelId)
+                    .setSmallIcon(R.drawable.sym_def_app_icon);
 
-        sNotificationManager.notify(1, notificationBuilder.build());
+            sNotificationManager.notify(1, notificationBuilder.build());
+        }
     }
 
 }
diff --git a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
index 7a2d164..ae7dcef 100644
--- a/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
+++ b/common/device-side/bedstead/nene/src/test/java/com/android/bedstead/nene/settings/SecureSettingsTest.java
@@ -52,6 +52,7 @@
     private static final String KEY = "key";
     private static final String INVALID_KEY = "noKey";
     private static final int INT_VALUE = 123;
+    private static final String STRING_VALUE = "String";
 
     @After
     public void resetSecureSettings() {
@@ -207,6 +208,153 @@
         });
     }
 
+    @Test
+    public void putString_putsStringIntoSecureSettingsOnInstrumentedUser() throws Exception {
+        TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putStringWithContentResolver_putsStringIntoSecureSettings() throws Exception {
+        TestApis.settings().secure().putString(sContext.getContentResolver(), KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void putStringWithContentResolver_preS_throwsException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () ->
+                TestApis.settings().secure().putString(
+                        sContext.getContentResolver(), KEY, STRING_VALUE));
+    }
+
+    @Test
+    public void putStringWithUser_instrumentedUser_putsStringIntoSecureSettings() throws Exception {
+        TestApis.settings().secure().putString(TestApis.users().instrumented(), KEY, STRING_VALUE);
+
+        assertThat(android.provider.Settings.Secure.getString(sContext.getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void putStringWithUser_differentUser_putsStringIntoSecureSettings() throws Exception {
+        TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+        try (PermissionContext p =
+                     TestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
+            assertThat(android.provider.Settings.Secure.getString(
+                    TestApis.context().androidContextAsUser(sDeviceState.secondaryUser())
+                            .getContentResolver(), KEY)).isEqualTo(STRING_VALUE);
+        }
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void putStringWithUser_differentUser_preS_throwsException() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () ->
+                TestApis.settings().secure()
+                        .putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE));
+    }
+
+    @Test
+    public void getString_getsStringFromSecureSettingsOnInstrumentedUser() {
+        TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().secure().getString(KEY)).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getString_invalidKey_returnsNull() {
+        assertThat(TestApis.settings().secure().getString(INVALID_KEY)).isNull();
+    }
+
+    @Test
+    public void getString_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().secure().getString(INVALID_KEY, STRING_VALUE)).isEqualTo(
+                STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getStringWithContentResolver_getsStringFromSecureSettings() {
+        TestApis.settings().secure().putString(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().secure().getString(
+                TestApis.context().instrumentedContext().getContentResolver(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void getStringWithContentResolver_preS_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> TestApis.settings().secure()
+                .getString(
+                        TestApis.context().instrumentedContext().getContentResolver(), KEY));
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getStringWithContentResolver_invalidKey_returnsNull() {
+        assertThat(TestApis.settings().secure().getString(
+                        TestApis.context().instrumentedContext().getContentResolver(),
+                        INVALID_KEY)).isNull();
+    }
+
+    @Test
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getStringWithContentResolver_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().secure().getString(
+                TestApis.context().instrumentedContext().getContentResolver(),
+                INVALID_KEY, STRING_VALUE)).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getStringWithUser_instrumentedUser_getsStringFromSecureSettings() {
+        TestApis.settings().secure().putString(KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().secure().getString(TestApis.users().instrumented(), KEY))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    public void getStringWithUser_invalidKey_returnsNull() {
+        assertThat(TestApis.settings().secure().getString(
+                        TestApis.users().instrumented(), INVALID_KEY)).isNull();
+    }
+
+    @Test
+    public void getStringWithUser_invalidKey_withDefault_returnsDefault() {
+        assertThat(TestApis.settings().secure().getString(
+                TestApis.users().instrumented(), INVALID_KEY, STRING_VALUE))
+                .isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(min = Build.VERSION_CODES.S)
+    public void getStringWithUser_differentUser_getsStringFromSecureSettings() {
+        TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+
+        assertThat(TestApis.settings().secure().getString(
+                sDeviceState.secondaryUser(), KEY)).isEqualTo(STRING_VALUE);
+    }
+
+    @Test
+    @EnsureHasSecondaryUser
+    @RequireSdkVersion(max = Build.VERSION_CODES.R)
+    public void getStringWithUser_differentUser_preS_throwsException() {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            TestApis.settings().secure().putString(sDeviceState.secondaryUser(), KEY, STRING_VALUE);
+        });
+    }
 
     // TODO(b/201319369): this requires a system user but should not
     @RequireRunOnSystemUser
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 4fbae6a..943aa4f 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
@@ -35,6 +35,7 @@
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
 import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
 import com.android.bedstead.harrier.annotations.RequireRunNotOnSecondaryUser;
 import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
 import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
@@ -51,12 +52,11 @@
 public class UserReferenceTest {
     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 static final String TEST_ACTIVITY_NAME = "com.android.bedstead.nene.test.Activity";
-    private static final int SERIAL_NO = 1000;
-    private static final UserType USER_TYPE = new UserType(new UserType.MutableUserType());
     private static final Context sContext = TestApis.context().instrumentedContext();
     private static final UserManager sUserManager = sContext.getSystemService(UserManager.class);
+    private static final String PASSWORD = "1234";
+    private static final String DIFFERENT_PASSWORD = "2345";
 
     @ClassRule @Rule
     public static final DeviceState sDeviceState = new DeviceState();
@@ -331,4 +331,59 @@
 
         assertThat(TestApis.users().all()).hasSize(numUsers);
     }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void setPassword_hasPassword() {
+        try {
+            TestApis.users().instrumented().setPassword(PASSWORD);
+
+            assertThat(TestApis.users().instrumented().hasPassword()).isTrue();
+        } finally {
+            TestApis.users().instrumented().clearPassword(PASSWORD);
+        }
+    }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void clearPassword_doesNotHavePassword() {
+        TestApis.users().instrumented().setPassword(PASSWORD);
+        TestApis.users().instrumented().clearPassword(PASSWORD);
+
+        assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+    }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void clearPassword_doesNotHavePassword_doesNothing() {
+        TestApis.users().instrumented().clearPassword(PASSWORD);
+
+        assertThat(TestApis.users().instrumented().hasPassword()).isFalse();
+    }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void clearPassword_incorrectOldPassword_throwsException() {
+        try {
+            TestApis.users().instrumented().setPassword(PASSWORD);
+
+            assertThrows(NeneException.class,
+                    () -> TestApis.users().instrumented().clearPassword(DIFFERENT_PASSWORD));
+        } finally {
+            TestApis.users().instrumented().clearPassword(PASSWORD);
+        }
+    }
+
+    @Test
+    @EnsurePasswordNotSet
+    public void setPassword_alreadyHasPassword_throwsException() {
+        try {
+            TestApis.users().instrumented().setPassword(PASSWORD);
+
+            assertThrows(NeneException.class,
+                    () -> TestApis.users().instrumented().setPassword(DIFFERENT_PASSWORD));
+        } finally {
+            TestApis.users().instrumented().clearPassword(PASSWORD);
+        }
+    }
 }
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 1769a01..d166477 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
@@ -21,8 +21,8 @@
 import static android.app.ActivityManager.STOP_USER_ON_SWITCH_TRUE;
 import static android.os.Build.VERSION.SDK_INT;
 
-import static com.android.bedstead.harrier.DeviceState.UserType.SYSTEM_USER;
 import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.SYSTEM_USER;
 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;
diff --git a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
index 15df572..aea1a1a 100644
--- a/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
+++ b/common/device-side/bedstead/queryable/src/main/java/com/android/queryable/Queryable.java
@@ -16,8 +16,6 @@
 
 package com.android.queryable;
 
-import androidx.annotation.Nullable;
-
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.stream.Collectors;
@@ -28,9 +26,9 @@
      *
      * <p>For example, if {@code fieldName} was age, we might generate "age > 5, age < 10"
      */
-    @Nullable
     String describeQuery(String fieldName);
 
+
     /**
      * Join sub-parts of a query for use in {@link #describeQuery(String)}.
      *
diff --git a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
index 01519a0..3dc6109 100644
--- a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
+++ b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpc.java
@@ -35,7 +35,7 @@
 import com.android.bedstead.testapp.TestAppProvider;
 
 /** Entry point to RemoteDPC. */
-public final class RemoteDpc extends RemotePolicyManager {
+public class RemoteDpc extends RemotePolicyManager {
 
     public static final ComponentName DPC_COMPONENT_NAME = new ComponentName(
             "com.android.RemoteDPC",
@@ -231,7 +231,7 @@
 
     private final DevicePolicyController mDevicePolicyController;
 
-    private RemoteDpc(DevicePolicyController devicePolicyController) {
+    RemoteDpc(DevicePolicyController devicePolicyController) {
         super(sTestApp, devicePolicyController == null ? null : devicePolicyController.user());
         mDevicePolicyController = devicePolicyController;
     }
diff --git a/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.java b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.java
new file mode 100644
index 0000000..0dd4e18
--- /dev/null
+++ b/common/device-side/bedstead/remotedpc/src/main/java/com/android/bedstead/remotedpc/RemoteDpcUsingParentInstance.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.bedstead.remotedpc;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.admin.RemoteDevicePolicyManager;
+import android.content.ComponentName;
+
+import com.android.bedstead.nene.devicepolicy.DevicePolicyController;
+
+/**
+ * Version of {@link RemoteDpc} which returns the result of calling
+ * {@link DevicePolicyManager#getParentProfileInstance(ComponentName)} when calling
+ * {@link #devicePolicyManager()}.
+ */
+public final class RemoteDpcUsingParentInstance extends RemoteDpc {
+
+    public RemoteDpcUsingParentInstance(
+            DevicePolicyController devicePolicyController) {
+        super(devicePolicyController);
+    }
+
+    @Override
+    public RemoteDevicePolicyManager devicePolicyManager() {
+        return super.devicePolicyManager().getParentProfileInstance(componentName());
+    }
+}
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
index 0a6bafb..75c5ddb 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Apis.java
@@ -137,7 +137,11 @@
             try {
                 // Strip "method" and semicolon
                 methodLine = methodLine.substring(7, methodLine.length() - 1);
-                methodSignatures.add(MethodSignature.forApiString(methodLine, types, elements));
+                MethodSignature signature =
+                        MethodSignature.forApiString(methodLine, types, elements);
+                if (signature != null) {
+                    methodSignatures.add(signature);
+                }
             } catch (RuntimeException e) {
                 throw new IllegalStateException("Error parsing method " + line, e);
             }
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
index aa10a8c..0735b68 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/MethodSignature.java
@@ -70,7 +70,8 @@
     /**
      * Create a {@link MethodSignature} for the given string from an API file.
      */
-    public static MethodSignature forApiString(String string, Types types, Elements elements) {
+    public static /* @Nullable */ MethodSignature forApiString(
+            String string, Types types, Elements elements) {
         // Strip annotations
         string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
         string = string.replaceAll("@.+? ", "");
@@ -93,6 +94,11 @@
             parts = string.split(" ", 2);
         }
 
+        if (string.startsWith("<")) {
+            // This includes type arguments, for now we ignore this method
+            return null;
+        }
+
         returnType = typeForString(parts[0], types, elements);
 
         string = parts[1];
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
index d93ff7e..ff35f1e 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/main/java/com/android/bedstead/remoteframeworkclasses/processor/Processor.java
@@ -38,6 +38,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -59,8 +60,10 @@
  *
  * <p>This is started by including the {@link RemoteFrameworkClasses} annotation.
  *
- * <p>For each entry in {@code FRAMEWORK_CLASSES} this will generate an interface including all public
- * and test APIs with the {@code CrossUser} annotation. This interface will be named the same as the
+ * <p>For each entry in {@code FRAMEWORK_CLASSES} this will generate an interface including all
+ * public
+ * and test APIs with the {@code CrossUser} annotation. This interface will be named the same as
+ * the
  * framework class except with a prefix of "Remote", and will be in the same package.
  *
  * <p>This will also generate an implementation of the interface which takes an instance of the
@@ -223,6 +226,85 @@
                     + ".PackageManager.OnChecksumsReadyListener) throws java.security.cert"
                     + ".CertificateEncodingException, android.content.pm.PackageManager"
                     + ".NameNotFoundException",
+            // Uses ComponentInfoFlags
+            "public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content"
+                    + ".ComponentName, @NonNull android.content.pm.PackageManager"
+                    + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content"
+                    + ".ComponentName, @NonNull android.content.pm.PackageManager"
+                    + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content"
+                    + ".ComponentName, @NonNull android.content.pm.PackageManager"
+                    + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content"
+                    + ".ComponentName, @NonNull android.content.pm.PackageManager"
+                    + ".ComponentInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public java.util.List<android.content.pm.ProviderInfo> queryContentProviders"
+                    + "(@Nullable String, int, @NonNull android.content.pm.PackageManager"
+                    + ".ComponentInfoFlags)",
+            "public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, "
+                    + "@NonNull android.content.pm.PackageManager.ComponentInfoFlags)",
+
+            // Uses ApplicationInfoFlags
+            "public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, "
+                    + "@NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws "
+                    + "android.content.pm.PackageManager.NameNotFoundException",
+            "public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications"
+                    + "(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags)",
+            "public java.util.List<android.content.pm.ApplicationInfo> "
+                    + "getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager"
+                    + ".ApplicationInfoFlags, int)",
+            // Uses PackageInfoFlags
+            "public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull "
+                    + "android.content.pm.PackageManager.PackageInfoFlags)",
+            "public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, "
+                    + "@NonNull android.content.pm.PackageManager.PackageInfoFlags)",
+            "public int[] getPackageGids(@NonNull String, @NonNull android.content.pm"
+                    + ".PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull "
+                    + "android.content.pm.PackageManager.PackageInfoFlags) throws android.content"
+                    + ".pm.PackageManager.NameNotFoundException",
+            "public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm"
+                    + ".VersionedPackage, @NonNull android.content.pm.PackageManager"
+                    + ".PackageInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager"
+                    + ".PackageInfoFlags) throws android.content.pm.PackageManager"
+                    + ".NameNotFoundException",
+            "public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions"
+                    + "(@NonNull String[], @NonNull android.content.pm.PackageManager"
+                    + ".PackageInfoFlags)",
+            "public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries"
+                    + "(@NonNull android.content.pm.PackageManager.PackageInfoFlags)",
+
+            // Uses ResolveInfoFlags
+            "public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers"
+                    + "(@NonNull android.content.Intent, @NonNull android.content.pm"
+                    + ".PackageManager.ResolveInfoFlags)",
+            "public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull"
+                    + " android.content.Intent, @NonNull android.content.pm.PackageManager"
+                    + ".ResolveInfoFlags)",
+
+            "public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions"
+                    + "(@Nullable android.content.ComponentName, @Nullable java.util.List<android"
+                    + ".content.Intent>, @NonNull android.content.Intent, @NonNull android"
+                    + ".content.pm.PackageManager.ResolveInfoFlags)",
+            "public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders"
+                    + "(@NonNull android.content.Intent, @NonNull android.content.pm"
+                    + ".PackageManager.ResolveInfoFlags)",
+            "public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull "
+                    + "android.content.Intent, @NonNull android.content.pm.PackageManager"
+                    + ".ResolveInfoFlags)",
+            "public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content"
+                    + ".Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags)",
+            "public android.content.pm.ResolveInfo resolveService(@NonNull android.content"
+                    + ".Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags)",
+
 
             // CrossProfileApps
 
@@ -245,9 +327,9 @@
 
             //Uses Drawable
             "public android.graphics.drawable.Drawable getShortcutBadgedIconDrawable("
-            + "android.content.pm.ShortcutInfo, int)",
+                    + "android.content.pm.ShortcutInfo, int)",
             "public android.graphics.drawable.Drawable getShortcutIconDrawable("
-            + "android.content.pm.ShortcutInfo, int)",
+                    + "android.content.pm.ShortcutInfo, int)",
 
             //Uses Executor
             "public void registerPackageInstallerSessionCallback("
@@ -276,36 +358,78 @@
             // AccountManager
 
             // Uses Activity
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession(android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties(String, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthTokenByFeatures(String, String, String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> startAddAccountSession(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> startUpdateCredentialsSession(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials(android.accounts.Account, String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> finishSession"
+                    + "(android.os.Bundle, android.app.Activity, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> editProperties"
+                    + "(String, android.app.Activity, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+                    + ".accounts.Account, String, android.os.Bundle, android.app.Activity, "
+                    + "android.accounts.AccountManagerCallback<android.os.Bundle>, android.os"
+                    + ".Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+                    + "getAuthTokenByFeatures(String, String, String[], android.app.Activity, "
+                    + "android.os.Bundle, android.os.Bundle, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+                    + "startAddAccountSession(String, String, String[], android.os.Bundle, "
+                    + "android.app.Activity, android.accounts.AccountManagerCallback<android.os"
+                    + ".Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> "
+                    + "startUpdateCredentialsSession(android.accounts.Account, String, android.os"
+                    + ".Bundle, android.app.Activity, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> updateCredentials"
+                    + "(android.accounts.Account, String, android.os.Bundle, android.app"
+                    + ".Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, "
+                    + "android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> confirmCredentials"
+                    + "(android.accounts.Account, android.os.Bundle, android.app.Activity, "
+                    + "android.accounts.AccountManagerCallback<android.os.Bundle>, android.os"
+                    + ".Handler)",
 
             // Uses OnAccountsUpdateListener
-            "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean)",
-            "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean, String[])",
-            "public void removeOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener)",
+            "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, "
+                    + "android.os.Handler, boolean)",
+            "public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, "
+                    + "android.os.Handler, boolean, String[])",
+            "public void removeOnAccountsUpdatedListener(android.accounts"
+                    + ".OnAccountsUpdateListener)",
 
             // Uses AccountManagerCallback
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android.accounts.Account, String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
-            "public android.os.Bundle hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.accounts.Account[]> getAccountsByTypeAndFeatures(String, String[], android.accounts.AccountManagerCallback<android.accounts.Account[]>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android.accounts.Account, String[], android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.AccountAuthenticatorResponse, android.accounts.Account, String) throws android.accounts.NetworkErrorException",
-            "public android.accounts.AccountManagerFuture<java.lang.Boolean> isCredentialsUpdateSuggested(android.accounts.Account, String, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<java.lang.Boolean> removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
-            "public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount(android.accounts.Account, @Size(min=1) String, android.accounts.AccountManagerCallback<android.accounts.Account>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+                    + ".accounts.Account, String, boolean, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+                    + ".accounts.Account, String, android.os.Bundle, boolean, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+                    + ".accounts.Account, String, boolean, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.os.Bundle> getAuthToken(android"
+                    + ".accounts.Account, String, android.os.Bundle, boolean, android.accounts"
+                    + ".AccountManagerCallback<android.os.Bundle>, android.os.Handler)",
+            "public android.os.Bundle hasFeatures(android.accounts.Account, String[], android"
+                    + ".accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
+            "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts.Account, "
+                    + "String, android.accounts.AccountManagerCallback<java.lang.Boolean>, "
+                    + "android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.accounts.Account[]> "
+                    + "getAccountsByTypeAndFeatures(String, String[], android.accounts"
+                    + ".AccountManagerCallback<android.accounts.Account[]>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<java.lang.Boolean> hasFeatures(android"
+                    + ".accounts.Account, String[], android.accounts.AccountManagerCallback<java"
+                    + ".lang.Boolean>, android.os.Handler)",
+            "public android.os.Bundle isCredentialsUpdateSuggested(android.accounts"
+                    + ".AccountAuthenticatorResponse, android.accounts.Account, String) throws "
+                    + "android.accounts.NetworkErrorException",
+            "public android.accounts.AccountManagerFuture<java.lang.Boolean> "
+                    + "isCredentialsUpdateSuggested(android.accounts.Account, String, android"
+                    + ".accounts.AccountManagerCallback<java.lang.Boolean>, android.os.Handler)",
+            "public android.accounts.AccountManagerFuture<android.accounts.Account> renameAccount"
+                    + "(android.accounts.Account, @Size(min=1) String, android.accounts"
+                    + ".AccountManagerCallback<android.accounts.Account>, android.os.Handler)",
 
             // Uses android.accounts.AccountManager
             "public static android.accounts.AccountManager get(android.content.Context)",
@@ -316,9 +440,13 @@
             "public void addContentView(android.view.View, android.view.ViewGroup.LayoutParams)",
             "@Nullable public android.view.View getCurrentFocus()",
             "@Nullable public android.view.View onCreatePanelView(int)",
-            "@Nullable public android.view.View onCreateView(@NonNull String, @NonNull android.content.Context, @NonNull android.util.AttributeSet)",
-            "@Nullable public android.view.View onCreateView(@Nullable android.view.View, @NonNull String, @NonNull android.content.Context, @NonNull android.util.AttributeSet)",
-            "public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android.view.Menu)",
+            "@Nullable public android.view.View onCreateView(@NonNull String, @NonNull android"
+                    + ".content.Context, @NonNull android.util.AttributeSet)",
+            "@Nullable public android.view.View onCreateView(@Nullable android.view.View, "
+                    + "@NonNull String, @NonNull android.content.Context, @NonNull android.util"
+                    + ".AttributeSet)",
+            "public boolean onPreparePanel(int, @Nullable android.view.View, @NonNull android"
+                    + ".view.Menu)",
             "public void openContextMenu(android.view.View)",
             "public void registerForContextMenu(android.view.View)",
             "public void setContentView(android.view.View)",
@@ -326,19 +454,29 @@
             "public void unregisterForContextMenu(android.view.View)",
 
             // Uses java.io.FileDescriptor
-            "public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[])",
+            "public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io"
+                    + ".PrintWriter, @Nullable String[])",
 
             // Uses android.app.Activity
             "@Deprecated public void finishActivityFromChild(@NonNull android.app.Activity, int)",
             "@Deprecated public void finishFromChild(android.app.Activity)",
             "public final android.app.Activity getParent()",
-            "@Deprecated public boolean navigateUpToFromChild(android.app.Activity, android.content.Intent)",
+            "@Deprecated public boolean navigateUpToFromChild(android.app.Activity, android"
+                    + ".content.Intent)",
             "protected void onChildTitleChanged(android.app.Activity, CharSequence)",
             "@Deprecated public boolean onNavigateUpFromChild(android.app.Activity)",
-            "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, @RequiresPermission android.content.Intent, int)",
-            "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, @RequiresPermission android.content.Intent, int, @Nullable android.os.Bundle)",
-            "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException",
-            "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, @Nullable android.os.Bundle) throws android.content.IntentSender.SendIntentException",
+            "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, "
+                    + "@RequiresPermission android.content.Intent, int)",
+            "@Deprecated public void startActivityFromChild(@NonNull android.app.Activity, "
+                    + "@RequiresPermission android.content.Intent, int, @Nullable android.os"
+                    + ".Bundle)",
+            "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android"
+                    + ".content.IntentSender, int, android.content.Intent, int, int, int) throws "
+                    + "android.content.IntentSender.SendIntentException",
+            "@Deprecated public void startIntentSenderFromChild(android.app.Activity, android"
+                    + ".content.IntentSender, int, android.content.Intent, int, int, int, "
+                    + "@Nullable android.os.Bundle) throws android.content.IntentSender"
+                    + ".SendIntentException",
 
             // Uses android.app.ActionBar
             "@Nullable public android.app.ActionBar getActionBar()",
@@ -348,8 +486,11 @@
 
             // Uses android.app.Fragment
             "@Deprecated public void onAttachFragment(android.app.Fragment)",
-            "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, @RequiresPermission android.content.Intent, int)",
-            "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, @RequiresPermission android.content.Intent, int, @Nullable android.os.Bundle)",
+            "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, "
+                    + "@RequiresPermission android.content.Intent, int)",
+            "@Deprecated public void startActivityFromFragment(@NonNull android.app.Fragment, "
+                    + "@RequiresPermission android.content.Intent, int, @Nullable android.os"
+                    + ".Bundle)",
 
             // Uses android.app.FragmentManager
             "@Deprecated public android.app.FragmentManager getFragmentManager()",
@@ -396,17 +537,22 @@
             "public android.view.WindowManager getWindowManager()",
 
             // Uses android.database.Cursor
-            "@Deprecated public final android.database.Cursor managedQuery(android.net.Uri, String[], String, String[], String)",
+            "@Deprecated public final android.database.Cursor managedQuery(android.net.Uri, "
+                    + "String[], String, String[], String)",
             "@Deprecated public void startManagingCursor(android.database.Cursor)",
             "@Deprecated public void stopManagingCursor(android.database.Cursor)",
 
             // Uses android.view.ActionMode
             "@CallSuper public void onActionModeFinished(android.view.ActionMode)",
             "@CallSuper public void onActionModeStarted(android.view.ActionMode)",
-            "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback)",
-            "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int)",
-            "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode.Callback)",
-            "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode.Callback, int)",
+            "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view"
+                    + ".ActionMode.Callback)",
+            "@Nullable public android.view.ActionMode onWindowStartingActionMode(android.view"
+                    + ".ActionMode.Callback, int)",
+            "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode"
+                    + ".Callback)",
+            "@Nullable public android.view.ActionMode startActionMode(android.view.ActionMode"
+                    + ".Callback, int)",
 
             // Uses android.view.MenuItem
             "public boolean onContextItemSelected(@NonNull android.view.MenuItem)",
@@ -415,13 +561,16 @@
             "public boolean onOptionsItemSelected(@NonNull android.view.MenuItem)",
 
             // Uses android.view.ContextMenu
-            "public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)",
+            "public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android"
+                    + ".view.ContextMenu.ContextMenuInfo)",
 
             // Uses android.app.Dialog
             "@Deprecated protected android.app.Dialog onCreateDialog(int)",
-            "@Deprecated @Nullable protected android.app.Dialog onCreateDialog(int, android.os.Bundle)",
+            "@Deprecated @Nullable protected android.app.Dialog onCreateDialog(int, android.os"
+                    + ".Bundle)",
             "@Deprecated protected void onPrepareDialog(int, android.app.Dialog)",
-            "@Deprecated protected void onPrepareDialog(int, android.app.Dialog, android.os.Bundle)",
+            "@Deprecated protected void onPrepareDialog(int, android.app.Dialog, android.os"
+                    + ".Bundle)",
 
             // Uses android.app.TaskStackBuilder
             "public void onCreateNavigateUpTaskStack(android.app.TaskStackBuilder)",
@@ -436,18 +585,24 @@
             "public boolean onPrepareOptionsMenu(android.view.Menu)",
 
             // Uses android.graphics.Canvas
-            "@Deprecated public boolean onCreateThumbnail(android.graphics.Bitmap, android.graphics.Canvas)",
+            "@Deprecated public boolean onCreateThumbnail(android.graphics.Bitmap, android"
+                    + ".graphics.Canvas)",
 
             // Uses android.os.CancellationSignal
-            "public void onGetDirectActions(@NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>)",
-            "public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>)",
+            "public void onGetDirectActions(@NonNull android.os.CancellationSignal, @NonNull java"
+                    + ".util.function.Consumer<java.util.List<android.app.DirectAction>>)",
+            "public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, "
+                    + "@NonNull android.os.CancellationSignal, @NonNull java.util.function"
+                    + ".Consumer<android.os.Bundle>)",
 
             // Uses android.view.SearchEvent
             "public boolean onSearchRequested(@Nullable android.view.SearchEvent)",
 
             // Uses android.app.Application.ActivityLifecycleCallbacks
-            "public void registerActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks)",
-            "public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application.ActivityLifecycleCallbacks)",
+            "public void registerActivityLifecycleCallbacks(@NonNull android.app.Application"
+                    + ".ActivityLifecycleCallbacks)",
+            "public void unregisterActivityLifecycleCallbacks(@NonNull android.app.Application"
+                    + ".ActivityLifecycleCallbacks)",
 
             // Uses Runnable
             "public final void runOnUiThread(Runnable)",
@@ -471,30 +626,45 @@
             "public abstract Object getSystemService(@NonNull String)",
 
             // ContextThemeWrapper
-            "protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean)",
+            "protected void onApplyThemeResource(android.content.res.Resources.Theme, int, "
+                    + "boolean)",
 
             // Context
 
             // Uses java.util.concurrent.Executor
-            "public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection)",
-            "public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection)",
+            "public boolean bindIsolatedService(@NonNull @RequiresPermission android.content"
+                    + ".Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, "
+                    + "@NonNull android.content.ServiceConnection)",
+            "public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int,"
+                    + " @NonNull java.util.concurrent.Executor, @NonNull android.content"
+                    + ".ServiceConnection)",
 
             // Uses android.content.ServiceConnection
-            "public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int)",
-            "public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle)",
+            "public abstract boolean bindService(@RequiresPermission android.content.Intent, "
+                    + "@NonNull android.content.ServiceConnection, int)",
+            "public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content"
+                    + ".Intent, @NonNull android.content.ServiceConnection, int, @NonNull android"
+                    + ".os.UserHandle)",
             "public abstract void unbindService(@NonNull android.content.ServiceConnection)",
             "public void updateServiceGroup(@NonNull android.content.ServiceConnection, int, int)",
 
             // Uses android.content.Context
             "@NonNull public android.content.Context createAttributionContext(@Nullable String)",
-            "public abstract android.content.Context createConfigurationContext(@NonNull android.content.res.Configuration)",
-            "@NonNull public android.content.Context createContext(@NonNull android.content.ContextParams)",
-            "public abstract android.content.Context createContextForSplit(String) throws android.content.pm.PackageManager.NameNotFoundException",
+            "public abstract android.content.Context createConfigurationContext(@NonNull android"
+                    + ".content.res.Configuration)",
+            "@NonNull public android.content.Context createContext(@NonNull android.content"
+                    + ".ContextParams)",
+            "public abstract android.content.Context createContextForSplit(String) throws android"
+                    + ".content.pm.PackageManager.NameNotFoundException",
             "public abstract android.content.Context createDeviceProtectedStorageContext()",
-            "@DisplayContext public abstract android.content.Context createDisplayContext(@NonNull android.view.Display)",
-            "public abstract android.content.Context createPackageContext(String, int) throws android.content.pm.PackageManager.NameNotFoundException",
-            "@NonNull @UiContext public android.content.Context createWindowContext(int, @Nullable android.os.Bundle)",
-            "@NonNull @UiContext public android.content.Context createWindowContext(@NonNull android.view.Display, int, @Nullable android.os.Bundle)",
+            "@DisplayContext public abstract android.content.Context createDisplayContext"
+                    + "(@NonNull android.view.Display)",
+            "public abstract android.content.Context createPackageContext(String, int) throws "
+                    + "android.content.pm.PackageManager.NameNotFoundException",
+            "@NonNull @UiContext public android.content.Context createWindowContext(int, "
+                    + "@Nullable android.os.Bundle)",
+            "@NonNull @UiContext public android.content.Context createWindowContext(@NonNull "
+                    + "android.view.Display, int, @Nullable android.os.Bundle)",
             "public abstract android.content.Context getApplicationContext()",
 
             // Uses android.content.res.AssetManager
@@ -507,7 +677,8 @@
             "@Nullable public android.view.Display getDisplay()",
 
             // Uses android.graphics.drawable.Drawable
-            "@Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes int)",
+            "@Nullable public final android.graphics.drawable.Drawable getDrawable(@DrawableRes "
+                    + "int)",
             "@Deprecated public abstract android.graphics.drawable.Drawable getWallpaper()",
             "@Deprecated public abstract android.graphics.drawable.Drawable peekWallpaper()",
 
@@ -533,39 +704,70 @@
             "public abstract android.content.res.Resources.Theme getTheme()",
 
             // Uses android.content.res.TypedArray
-            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull @StyleableRes int[])",
-            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@StyleRes int, @NonNull @StyleableRes int[]) throws android.content.res.Resources.NotFoundException",
-            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[])",
-            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[], @AttrRes int, @StyleRes int)",
+            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes(@NonNull"
+                    + " @StyleableRes int[])",
+            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+                    + "(@StyleRes int, @NonNull @StyleableRes int[]) throws android.content.res"
+                    + ".Resources.NotFoundException",
+            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+                    + "(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[])",
+            "@NonNull public final android.content.res.TypedArray obtainStyledAttributes"
+                    + "(@Nullable android.util.AttributeSet, @NonNull @StyleableRes int[], "
+                    + "@AttrRes int, @StyleRes int)",
 
             // Uses java.io.FileInputStream
-            "public abstract java.io.FileInputStream openFileInput(String) throws java.io.FileNotFoundException",
+            "public abstract java.io.FileInputStream openFileInput(String) throws java.io"
+                    + ".FileNotFoundException",
 
             // Uses java.io.FileOutputStream
-            "public abstract java.io.FileOutputStream openFileOutput(String, int) throws java.io.FileNotFoundException",
+            "public abstract java.io.FileOutputStream openFileOutput(String, int) throws java.io"
+                    + ".FileNotFoundException",
 
             // Uses android.database.sqlite.SQLiteDatabase
-            "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, int, android.database.sqlite.SQLiteDatabase.CursorFactory)",
-            "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, int, android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable android.database.DatabaseErrorHandler)",
+            "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, "
+                    + "int, android.database.sqlite.SQLiteDatabase.CursorFactory)",
+            "public abstract android.database.sqlite.SQLiteDatabase openOrCreateDatabase(String, "
+                    + "int, android.database.sqlite.SQLiteDatabase.CursorFactory, @Nullable "
+                    + "android.database.DatabaseErrorHandler)",
 
             // Uses android.content.ComponentCallbacks
             "public void registerComponentCallbacks(android.content.ComponentCallbacks)",
             "public void unregisterComponentCallbacks(android.content.ComponentCallbacks)",
 
             // Uses android.content.BroadcastReceiver
-            "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter)",
-            "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android.content.BroadcastReceiver, android.content.IntentFilter, int)",
-            "@Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler)",
-            "@Nullable public abstract android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int)",
-            "public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android.content.Intent, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
-            "public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
-            "public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
-            "public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content.Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
-            "public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
+            "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android"
+                    + ".content.BroadcastReceiver, android.content.IntentFilter)",
+            "@Nullable public abstract android.content.Intent registerReceiver(@Nullable android"
+                    + ".content.BroadcastReceiver, android.content.IntentFilter, int)",
+            "@Nullable public abstract android.content.Intent registerReceiver(android.content"
+                    + ".BroadcastReceiver, android.content.IntentFilter, @Nullable String, "
+                    + "@Nullable android.os.Handler)",
+            "@Nullable public abstract android.content.Intent registerReceiver(android.content"
+                    + ".BroadcastReceiver, android.content.IntentFilter, @Nullable String, "
+                    + "@Nullable android.os.Handler, int)",
+            "public abstract void sendOrderedBroadcast(@NonNull @RequiresPermission android"
+                    + ".content.Intent, @Nullable String, @Nullable android.content"
+                    + ".BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, "
+                    + "@Nullable android.os.Bundle)",
+            "public void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, "
+                    + "@Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable "
+                    + "android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle)",
+            "public abstract void sendOrderedBroadcastAsUser(@RequiresPermission android.content"
+                    + ".Intent, android.os.UserHandle, @Nullable String, android.content"
+                    + ".BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, "
+                    + "@Nullable android.os.Bundle)",
+            "public abstract void sendStickyOrderedBroadcast(@RequiresPermission android.content"
+                    + ".Intent, android.content.BroadcastReceiver, @Nullable android.os.Handler, "
+                    + "int, @Nullable String, @Nullable android.os.Bundle)",
+            "public abstract void sendStickyOrderedBroadcastAsUser(@RequiresPermission android"
+                    + ".content.Intent, android.os.UserHandle, android.content.BroadcastReceiver,"
+                    + " @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os"
+                    + ".Bundle)",
             "public abstract void unregisterReceiver(android.content.BroadcastReceiver)",
 
             // Uses java.io.InputStream
-            "@Deprecated public abstract void setWallpaper(java.io.InputStream) throws java.io.IOException",
+            "@Deprecated public abstract void setWallpaper(java.io.InputStream) throws java.io"
+                    + ".IOException",
 
 
             // Doesn't make sense as it requires an actual Context
@@ -579,58 +781,100 @@
             "public static void removeStatusChangeListener(Object)",
 
             // Uses android.content.ContentProviderClient
-            "@Nullable public final android.content.ContentProviderClient acquireContentProviderClient(@NonNull android.net.Uri)",
-            "@Nullable public final android.content.ContentProviderClient acquireContentProviderClient(@NonNull String)",
-            "@Nullable public final android.content.ContentProviderClient acquireUnstableContentProviderClient(@NonNull android.net.Uri)",
-            "@Nullable public final android.content.ContentProviderClient acquireUnstableContentProviderClient(@NonNull String)",
+            "@Nullable public final android.content.ContentProviderClient "
+                    + "acquireContentProviderClient(@NonNull android.net.Uri)",
+            "@Nullable public final android.content.ContentProviderClient "
+                    + "acquireContentProviderClient(@NonNull String)",
+            "@Nullable public final android.content.ContentProviderClient "
+                    + "acquireUnstableContentProviderClient(@NonNull android.net.Uri)",
+            "@Nullable public final android.content.ContentProviderClient "
+                    + "acquireUnstableContentProviderClient(@NonNull String)",
 
             // Uses android.content.ContentResolver.MimeTypeInfo
-            "@NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo(@NonNull String)",
+            "@NonNull public final android.content.ContentResolver.MimeTypeInfo getTypeInfo"
+                    + "(@NonNull String)",
 
             // Uses android.util.Size
-            "@NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, @NonNull android.util.Size, @Nullable android.os.CancellationSignal) throws java.io.IOException",
+            "@NonNull public android.graphics.Bitmap loadThumbnail(@NonNull android.net.Uri, "
+                    + "@NonNull android.util.Size, @Nullable android.os.CancellationSignal) "
+                    + "throws java.io.IOException",
 
             // Uses android.database.ContentObserver
-            "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver)",
-            "@Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, boolean)",
-            "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database.ContentObserver, int)",
-            "public void notifyChange(@NonNull java.util.Collection<android.net.Uri>, @Nullable android.database.ContentObserver, int)",
-            "public final void registerContentObserver(@NonNull android.net.Uri, boolean, @NonNull android.database.ContentObserver)",
-            "public final void unregisterContentObserver(@NonNull android.database.ContentObserver)",
+            "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database"
+                    + ".ContentObserver)",
+            "@Deprecated public void notifyChange(@NonNull android.net.Uri, @Nullable android"
+                    + ".database.ContentObserver, boolean)",
+            "public void notifyChange(@NonNull android.net.Uri, @Nullable android.database"
+                    + ".ContentObserver, int)",
+            "public void notifyChange(@NonNull java.util.Collection<android.net.Uri>, @Nullable "
+                    + "android.database.ContentObserver, int)",
+            "public final void registerContentObserver(@NonNull android.net.Uri, boolean, "
+                    + "@NonNull android.database.ContentObserver)",
+            "public final void unregisterContentObserver(@NonNull android.database"
+                    + ".ContentObserver)",
 
             // Uses android.os.CancellationSignal
-            "@Nullable public final android.content.res.AssetFileDescriptor openAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "@Nullable public final android.content.res.AssetFileDescriptor openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "@Nullable public final android.os.ParcelFileDescriptor openFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "@Nullable public final android.os.ParcelFileDescriptor openFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFile(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) throws java.io.FileNotFoundException",
-            "public final boolean refresh(@NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal)",
+            "@Nullable public final android.content.res.AssetFileDescriptor openAssetFile"
+                    + "(@NonNull android.net.Uri, @NonNull String, @Nullable android.os"
+                    + ".CancellationSignal) throws java.io.FileNotFoundException",
+            "@Nullable public final android.content.res.AssetFileDescriptor "
+                    + "openAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, "
+                    + "@Nullable android.os.CancellationSignal) throws java.io"
+                    + ".FileNotFoundException",
+            "@Nullable public final android.os.ParcelFileDescriptor openFile(@NonNull android.net"
+                    + ".Uri, @NonNull String, @Nullable android.os.CancellationSignal) throws "
+                    + "java.io.FileNotFoundException",
+            "@Nullable public final android.os.ParcelFileDescriptor openFileDescriptor(@NonNull "
+                    + "android.net.Uri, @NonNull String, @Nullable android.os.CancellationSignal)"
+                    + " throws java.io.FileNotFoundException",
+            "@Nullable public final android.content.res.AssetFileDescriptor openTypedAssetFile"
+                    + "(@NonNull android.net.Uri, @NonNull String, @Nullable android.os.Bundle, "
+                    + "@Nullable android.os.CancellationSignal) throws java.io"
+                    + ".FileNotFoundException",
+            "@Nullable public final android.content.res.AssetFileDescriptor "
+                    + "openTypedAssetFileDescriptor(@NonNull android.net.Uri, @NonNull String, "
+                    + "@Nullable android.os.Bundle, @Nullable android.os.CancellationSignal) "
+                    + "throws java.io.FileNotFoundException",
+            "public final boolean refresh(@NonNull android.net.Uri, @Nullable android.os.Bundle, "
+                    + "@Nullable android.os.CancellationSignal)",
 
             // Uses java.io.InputStream
-            "@Nullable public final java.io.InputStream openInputStream(@NonNull android.net.Uri) throws java.io.FileNotFoundException",
+            "@Nullable public final java.io.InputStream openInputStream(@NonNull android.net.Uri)"
+                    + " throws java.io.FileNotFoundException",
 
             // Uses java.io.OutputStream
-            "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net.Uri) throws java.io.FileNotFoundException",
-            "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net.Uri, @NonNull String) throws java.io.FileNotFoundException",
+            "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net"
+                    + ".Uri) throws java.io.FileNotFoundException",
+            "@Nullable public final java.io.OutputStream openOutputStream(@NonNull android.net"
+                    + ".Uri, @NonNull String) throws java.io.FileNotFoundException",
 
             // Uses android.database.Cursor
-            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String)",
-            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal)",
-            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission.Read android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal)",
+            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+                    + ".Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable "
+                    + "String[], @Nullable String)",
+            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+                    + ".Read android.net.Uri, @Nullable String[], @Nullable String, @Nullable "
+                    + "String[], @Nullable String, @Nullable android.os.CancellationSignal)",
+            "@Nullable public final android.database.Cursor query(@NonNull @RequiresPermission"
+                    + ".Read android.net.Uri, @Nullable String[], @Nullable android.os.Bundle, "
+                    + "@Nullable android.os.CancellationSignal)",
 
             // Uses android.content.ContentResolver
-            "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProvider)",
-            "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content.ContentProviderClient)",
+            "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content"
+                    + ".ContentProvider)",
+            "@NonNull public static android.content.ContentResolver wrap(@NonNull android.content"
+                    + ".ContentProviderClient)",
 
 
             // KeyChain
 
             // Uses android.app.Activity
-            "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable String, int, @Nullable String)",
-            "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java.security.Principal[], @Nullable android.net.Uri, @Nullable String)"
-
-
+            "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull "
+                    + "android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java"
+                    + ".security.Principal[], @Nullable String, int, @Nullable String)",
+            "public static void choosePrivateKeyAlias(@NonNull android.app.Activity, @NonNull "
+                    + "android.security.KeyChainAliasCallback, @Nullable String[], @Nullable java"
+                    + ".security.Principal[], @Nullable android.net.Uri, @Nullable String)"
 
 
     );
@@ -641,6 +885,19 @@
     private static final ClassName NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME =
             ClassName.get("com.android.bedstead.remoteframeworkclasses",
                     "NullParcelableRemoteContentResolver");
+
+    // TODO(b/205562849): These only support passing null, which is fine for existing tests but
+    //  will be misleading
+    private static final ClassName NULL_PARCELABLE_ACTIVITY_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableActivity");
+    private static final ClassName NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableAccountManagerCallback");
+    private static final ClassName NULL_HANDLER_CALLBACK_CLASSNAME =
+            ClassName.get("com.android.bedstead.remoteframeworkclasses",
+                    "NullParcelableHandler");
+
     private static final ClassName COMPONENT_NAME_CLASSNAME =
             ClassName.get("android.content", "ComponentName");
 
@@ -660,6 +917,7 @@
             Set<MethodSignature> blocklistedMethodSignatures = BLOCKLISTED_METHODS.stream()
                     .map(m -> MethodSignature.forApiString(
                             m, processingEnv.getTypeUtils(), processingEnv.getElementUtils()))
+                    .filter(Objects::nonNull)
                     .collect(Collectors.toSet());
 
             for (String systemService : FRAMEWORK_CLASSES) {
@@ -678,6 +936,9 @@
     private void generateWrappers() {
         generateWrapper(NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME);
         generateWrapper(NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME);
+        generateWrapper(NULL_PARCELABLE_ACTIVITY_CLASSNAME);
+        generateWrapper(NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME);
+        generateWrapper(NULL_HANDLER_CALLBACK_CLASSNAME);
     }
 
     private void generateWrapper(ClassName className) {
@@ -712,7 +973,7 @@
             Set<MethodSignature> blocklistedMethodSignatures,
             Elements elements) {
         Set<ExecutableElement> methods = filterMethods(getMethods(frameworkClass,
-                processingEnv.getElementUtils()),
+                        processingEnv.getElementUtils()),
                 Apis.forClass(frameworkClass.getQualifiedName().toString(),
                         processingEnv.getTypeUtils(), processingEnv.getElementUtils()), elements)
                 .stream()
@@ -761,9 +1022,13 @@
 
 
         classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
-                .addMember("parcelableWrappers", "{$T.class, $T.class}",
+                .addMember("parcelableWrappers",
+                        "{$T.class, $T.class, $T.class, $T.class, $T.class}",
                         NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
-                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME,
+                        NULL_PARCELABLE_ACTIVITY_CLASSNAME,
+                        NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME,
+                        NULL_HANDLER_CALLBACK_CLASSNAME)
                 .addMember("futureWrappers", "$T.class",
                         ACCOUNT_MANAGE_FUTURE_WRAPPER_CLASSNAME)
                 .build());
@@ -815,9 +1080,13 @@
                 TypeSpec.classBuilder(className).addModifiers(Modifier.FINAL, Modifier.PUBLIC);
 
         classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class)
-                .addMember("parcelableWrappers", "{$T.class, $T.class}",
+                .addMember("parcelableWrappers",
+                        "{$T.class, $T.class, $T.class, $T.class, $T.class}",
                         NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME,
-                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME)
+                        NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME,
+                        NULL_PARCELABLE_ACTIVITY_CLASSNAME,
+                        NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME,
+                        NULL_HANDLER_CALLBACK_CLASSNAME)
                 .build());
 
         classBuilder.addField(ClassName.get(frameworkClass),
@@ -909,7 +1178,7 @@
                 "Remote" + frameworkClass.getSimpleName().toString() + "Impl");
         TypeSpec.Builder classBuilder =
                 TypeSpec.classBuilder(
-                        className)
+                                className)
                         .addSuperinterface(interfaceClassName)
                         .addModifiers(Modifier.PUBLIC);
 
@@ -960,9 +1229,12 @@
             if (signatureReturnOverrides.containsKey(signature)) {
                 methodBuilder.returns(signatureReturnOverrides.get(signature));
                 methodBuilder.addStatement(
-                        "return new $TImpl($L.$L($L))",
+                        "$TImpl ret = new $TImpl($L.$L($L))",
+                        signatureReturnOverrides.get(signature),
                         signatureReturnOverrides.get(signature), frameworkClassName,
                         method.getSimpleName(), String.join(", ", paramNames));
+                // We assume all replacements are null-only
+                methodBuilder.addStatement("return null");
             } else if (method.getReturnType().getKind().equals(TypeKind.VOID)) {
                 methodBuilder.addStatement(
                         "$L.$L($L)",
@@ -1040,6 +1312,6 @@
     private String methodHash(ExecutableElement method) {
         return method.getSimpleName() + "(" + method.getParameters().stream()
                 .map(p -> p.asType().toString()).collect(
-                Collectors.joining(",")) + ")";
+                        Collectors.joining(",")) + ")";
     }
 }
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
index adabda3..5984cdd 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/current.txt
@@ -61,6 +61,7 @@
     field public static final String BLUETOOTH_PRIVILEGED = "android.permission.BLUETOOTH_PRIVILEGED";
     field public static final String BLUETOOTH_SCAN = "android.permission.BLUETOOTH_SCAN";
     field public static final String BODY_SENSORS = "android.permission.BODY_SENSORS";
+    field public static final String BODY_SENSORS_BACKGROUND = "android.permission.BODY_SENSORS_BACKGROUND";
     field public static final String BROADCAST_PACKAGE_REMOVED = "android.permission.BROADCAST_PACKAGE_REMOVED";
     field public static final String BROADCAST_SMS = "android.permission.BROADCAST_SMS";
     field public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
@@ -79,6 +80,7 @@
     field public static final String CONTROL_LOCATION_UPDATES = "android.permission.CONTROL_LOCATION_UPDATES";
     field public static final String DELETE_CACHE_FILES = "android.permission.DELETE_CACHE_FILES";
     field public static final String DELETE_PACKAGES = "android.permission.DELETE_PACKAGES";
+    field public static final String DELIVER_COMPANION_MESSAGES = "android.permission.DELIVER_COMPANION_MESSAGES";
     field public static final String DIAGNOSTIC = "android.permission.DIAGNOSTIC";
     field public static final String DISABLE_KEYGUARD = "android.permission.DISABLE_KEYGUARD";
     field public static final String DUMP = "android.permission.DUMP";
@@ -99,6 +101,7 @@
     field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
     field public static final String INTERNET = "android.permission.INTERNET";
     field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
+    field public static final String LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK = "android.permission.LAUNCH_MULTI_PANE_SETTINGS_DEEP_LINK";
     field public static final String LOADER_USAGE_STATS = "android.permission.LOADER_USAGE_STATS";
     field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
     field public static final String MANAGE_DOCUMENTS = "android.permission.MANAGE_DOCUMENTS";
@@ -112,19 +115,23 @@
     field public static final String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
     field public static final String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
     field public static final String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
+    field public static final String NEARBY_WIFI_DEVICES = "android.permission.NEARBY_WIFI_DEVICES";
     field public static final String NFC = "android.permission.NFC";
     field public static final String NFC_PREFERRED_PAYMENT_INFO = "android.permission.NFC_PREFERRED_PAYMENT_INFO";
     field public static final String NFC_TRANSACTION_EVENT = "android.permission.NFC_TRANSACTION_EVENT";
     field public static final String PACKAGE_USAGE_STATS = "android.permission.PACKAGE_USAGE_STATS";
     field @Deprecated public static final String PERSISTENT_ACTIVITY = "android.permission.PERSISTENT_ACTIVITY";
+    field public static final String POST_NOTIFICATIONS = "android.permission.POST_NOTIFICATIONS";
     field @Deprecated public static final String PROCESS_OUTGOING_CALLS = "android.permission.PROCESS_OUTGOING_CALLS";
     field public static final String QUERY_ALL_PACKAGES = "android.permission.QUERY_ALL_PACKAGES";
+    field public static final String READ_BASIC_PHONE_STATE = "android.permission.READ_BASIC_PHONE_STATE";
     field public static final String READ_CALENDAR = "android.permission.READ_CALENDAR";
     field public static final String READ_CALL_LOG = "android.permission.READ_CALL_LOG";
     field public static final String READ_CONTACTS = "android.permission.READ_CONTACTS";
     field public static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
     field @Deprecated public static final String READ_INPUT_STATE = "android.permission.READ_INPUT_STATE";
     field public static final String READ_LOGS = "android.permission.READ_LOGS";
+    field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
     field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
@@ -165,6 +172,7 @@
     field public static final String SIGNAL_PERSISTENT_PROCESSES = "android.permission.SIGNAL_PERSISTENT_PROCESSES";
     field @Deprecated public static final String SMS_FINANCIAL_TRANSACTIONS = "android.permission.SMS_FINANCIAL_TRANSACTIONS";
     field public static final String START_FOREGROUND_SERVICES_FROM_BACKGROUND = "android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND";
+    field public static final String START_VIEW_APP_FEATURES = "android.permission.START_VIEW_APP_FEATURES";
     field public static final String START_VIEW_PERMISSION_USAGE = "android.permission.START_VIEW_PERMISSION_USAGE";
     field public static final String STATUS_BAR = "android.permission.STATUS_BAR";
     field public static final String SYSTEM_ALERT_WINDOW = "android.permission.SYSTEM_ALERT_WINDOW";
@@ -202,6 +210,7 @@
     field public static final String LOCATION = "android.permission-group.LOCATION";
     field public static final String MICROPHONE = "android.permission-group.MICROPHONE";
     field public static final String NEARBY_DEVICES = "android.permission-group.NEARBY_DEVICES";
+    field public static final String NOTIFICATIONS = "android.permission-group.NOTIFICATIONS";
     field public static final String PHONE = "android.permission-group.PHONE";
     field public static final String SENSORS = "android.permission-group.SENSORS";
     field public static final String SMS = "android.permission-group.SMS";
@@ -405,6 +414,7 @@
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
     field public static final int canControlMagnification = 16844039; // 0x1010507
+    field public static final int canDisplayOnRemoteDevices;
     field public static final int canPauseRecording = 16844314; // 0x101061a
     field public static final int canPerformGestures = 16844045; // 0x101050d
     field public static final int canRecord = 16844060; // 0x101051c
@@ -841,7 +851,7 @@
     field @Deprecated public static final int isModifier = 16843334; // 0x1010246
     field @Deprecated public static final int isRepeatable = 16843336; // 0x1010248
     field public static final int isScrollContainer = 16843342; // 0x101024e
-    field public static final int isSplitRequired = 16844177; // 0x1010591
+    field @Deprecated public static final int isSplitRequired = 16844177; // 0x1010591
     field public static final int isStatic = 16844122; // 0x101055a
     field @Deprecated public static final int isSticky = 16843335; // 0x1010247
     field public static final int isolatedProcess = 16843689; // 0x10103a9
@@ -1194,8 +1204,10 @@
     field public static final int requiredFeature = 16844116; // 0x1010554
     field public static final int requiredForAllUsers = 16843728; // 0x10103d0
     field public static final int requiredNotFeature = 16844117; // 0x1010555
+    field public static final int requiredSplitTypes;
     field public static final int requiresFadingEdge = 16843685; // 0x10103a5
     field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
+    field public static final int resetEnabledSettingsOnAppDataCleared;
     field public static final int resizeClip = 16843983; // 0x10104cf
     field public static final int resizeMode = 16843619; // 0x1010363
     field public static final int resizeable = 16843405; // 0x101028d
@@ -1291,12 +1303,14 @@
     field public static final int shareInterpolator = 16843195; // 0x10101bb
     field @Deprecated public static final int sharedUserId = 16842763; // 0x101000b
     field @Deprecated public static final int sharedUserLabel = 16843361; // 0x1010261
+    field public static final int sharedUserMaxSdkVersion;
     field public static final int shell = 16844180; // 0x1010594
     field public static final int shortcutDisabledMessage = 16844075; // 0x101052b
     field public static final int shortcutId = 16844072; // 0x1010528
     field public static final int shortcutLongLabel = 16844074; // 0x101052a
     field public static final int shortcutShortLabel = 16844073; // 0x1010529
     field public static final int shouldDisableView = 16843246; // 0x10101ee
+    field public static final int shouldUseDefaultUnfoldTransition = 16844364; // 0x101064c
     field public static final int showAsAction = 16843481; // 0x10102d9
     field public static final int showDefault = 16843258; // 0x10101fa
     field public static final int showDividers = 16843561; // 0x1010329
@@ -1328,6 +1342,7 @@
     field public static final int splitMotionEvents = 16843503; // 0x10102ef
     field public static final int splitName = 16844105; // 0x1010549
     field public static final int splitTrack = 16843852; // 0x101044c
+    field public static final int splitTypes;
     field public static final int spotShadowAlpha = 16843967; // 0x10104bf
     field public static final int src = 16843033; // 0x1010119
     field public static final int ssp = 16843747; // 0x10103e3
@@ -1398,6 +1413,7 @@
     field public static final int summaryColumn = 16843426; // 0x10102a2
     field public static final int summaryOff = 16843248; // 0x10101f0
     field public static final int summaryOn = 16843247; // 0x10101ef
+    field public static final int supportedTypes;
     field public static final int supportsAssist = 16844016; // 0x10104f0
     field public static final int supportsInlineSuggestions = 16844301; // 0x101060d
     field public static final int supportsLaunchVoiceAssistFromKeyguard = 16844017; // 0x10104f1
@@ -1405,6 +1421,7 @@
     field public static final int supportsMultipleDisplays = 16844182; // 0x1010596
     field public static final int supportsPictureInPicture = 16844023; // 0x10104f7
     field public static final int supportsRtl = 16843695; // 0x10103af
+    field public static final int supportsStylusHandwriting;
     field public static final int supportsSwitchingToNextInputMethod = 16843755; // 0x10103eb
     field public static final int supportsUploading = 16843419; // 0x101029b
     field public static final int suppressesSpellChecker = 16844355; // 0x1010643
@@ -2012,6 +2029,9 @@
   public static final class R.id {
     ctor public R.id();
     field public static final int accessibilityActionContextClick = 16908348; // 0x102003c
+    field public static final int accessibilityActionDragCancel = 16908375; // 0x1020057
+    field public static final int accessibilityActionDragDrop = 16908374; // 0x1020056
+    field public static final int accessibilityActionDragStart = 16908373; // 0x1020055
     field public static final int accessibilityActionHideTooltip = 16908357; // 0x1020045
     field public static final int accessibilityActionImeEnter = 16908372; // 0x1020054
     field public static final int accessibilityActionMoveWindow = 16908354; // 0x1020042
@@ -2027,7 +2047,12 @@
     field public static final int accessibilityActionScrollUp = 16908344; // 0x1020038
     field public static final int accessibilityActionSetProgress = 16908349; // 0x102003d
     field public static final int accessibilityActionShowOnScreen = 16908342; // 0x1020036
+    field public static final int accessibilityActionShowSuggestions;
     field public static final int accessibilityActionShowTooltip = 16908356; // 0x1020044
+    field public static final int accessibilityActionSwipeDown;
+    field public static final int accessibilityActionSwipeLeft;
+    field public static final int accessibilityActionSwipeRight;
+    field public static final int accessibilityActionSwipeUp;
     field public static final int accessibilitySystemActionBack = 16908363; // 0x102004b
     field public static final int accessibilitySystemActionHome = 16908364; // 0x102004c
     field public static final int accessibilitySystemActionLockScreen = 16908370; // 0x1020052
@@ -2977,6 +3002,7 @@
   }
 
   public final class AccessibilityGestureEvent implements android.os.Parcelable {
+    ctor public AccessibilityGestureEvent(int, int, @NonNull java.util.List<android.view.MotionEvent>);
     method public int describeContents();
     method @NonNull public static String gestureIdToString(int);
     method public int getDisplayId();
@@ -2988,6 +3014,8 @@
 
   public abstract class AccessibilityService extends android.app.Service {
     ctor public AccessibilityService();
+    method public boolean clearCache();
+    method public boolean clearCachedSubtree(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public final void disableSelf();
     method public final boolean dispatchGesture(@NonNull android.accessibilityservice.GestureDescription, @Nullable android.accessibilityservice.AccessibilityService.GestureResultCallback, @Nullable android.os.Handler);
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
@@ -2999,8 +3027,11 @@
     method public final android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
     method @NonNull public final android.accessibilityservice.AccessibilityService.SoftKeyboardController getSoftKeyboardController();
     method @NonNull public final java.util.List<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> getSystemActions();
+    method @NonNull public final android.accessibilityservice.TouchInteractionController getTouchInteractionController(int);
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
     method @NonNull public final android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
+    method public boolean isCacheEnabled();
+    method public boolean isNodeInCache(@NonNull android.view.accessibility.AccessibilityNodeInfo);
     method public abstract void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     method public final android.os.IBinder onBind(android.content.Intent);
     method @Deprecated protected boolean onGesture(int);
@@ -3011,6 +3042,7 @@
     method public void onSystemActionsChanged();
     method public final boolean performGlobalAction(int);
     method public void setAccessibilityFocusAppearance(int, @ColorInt int);
+    method public boolean setCacheEnabled(boolean);
     method public void setGestureDetectionPassthroughRegion(int, @NonNull android.graphics.Region);
     method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
     method public void setTouchExplorationPassthroughRegion(int, @NonNull android.graphics.Region);
@@ -3098,11 +3130,13 @@
     method public void addListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener, @Nullable android.os.Handler);
     method public float getCenterX();
     method public float getCenterY();
+    method @Nullable public android.accessibilityservice.MagnificationConfig getMagnificationConfig();
     method @NonNull public android.graphics.Region getMagnificationRegion();
     method public float getScale();
     method public boolean removeListener(@NonNull android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener);
     method public boolean reset(boolean);
     method public boolean setCenter(float, float, boolean);
+    method public boolean setMagnificationConfig(@NonNull android.accessibilityservice.MagnificationConfig, boolean);
     method public boolean setScale(float, boolean);
   }
 
@@ -3121,8 +3155,12 @@
     method public void addOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener, @Nullable android.os.Handler);
     method public int getShowMode();
     method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
+    method @CheckResult public int setInputMethodEnabled(@NonNull String, boolean) throws java.lang.SecurityException;
     method public boolean setShowMode(int);
     method public boolean switchToInputMethod(@NonNull String);
+    field public static final int ENABLE_IME_FAIL_BY_ADMIN = 1; // 0x1
+    field public static final int ENABLE_IME_FAIL_UNKNOWN = 2; // 0x2
+    field public static final int ENABLE_IME_SUCCESS = 0; // 0x0
   }
 
   public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -3233,6 +3271,53 @@
     method public boolean willContinue();
   }
 
+  public final class MagnificationConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method public float getCenterX();
+    method public float getCenterY();
+    method public int getMode();
+    method public float getScale();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.MagnificationConfig> CREATOR;
+    field public static final int MAGNIFICATION_MODE_DEFAULT = 0; // 0x0
+    field public static final int MAGNIFICATION_MODE_FULLSCREEN = 1; // 0x1
+    field public static final int MAGNIFICATION_MODE_WINDOW = 2; // 0x2
+  }
+
+  public static final class MagnificationConfig.Builder {
+    ctor public MagnificationConfig.Builder();
+    method @NonNull public android.accessibilityservice.MagnificationConfig build();
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterX(float);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setCenterY(float);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setMode(int);
+    method @NonNull public android.accessibilityservice.MagnificationConfig.Builder setScale(@FloatRange(from=1.0f, to=8.0f) float);
+  }
+
+  public final class TouchInteractionController {
+    method public int getDisplayId();
+    method public int getMaxPointerCount();
+    method public int getState();
+    method public void performClick();
+    method public void performLongClickAndStartDrag();
+    method public void registerCallback(@Nullable java.util.concurrent.Executor, @NonNull android.accessibilityservice.TouchInteractionController.Callback);
+    method public void requestDelegating();
+    method public void requestDragging(int);
+    method public void requestTouchExploration();
+    method @NonNull public static String stateToString(int);
+    method public void unregisterAllCallbacks();
+    method public boolean unregisterCallback(@NonNull android.accessibilityservice.TouchInteractionController.Callback);
+    field public static final int STATE_CLEAR = 0; // 0x0
+    field public static final int STATE_DELEGATING = 4; // 0x4
+    field public static final int STATE_DRAGGING = 3; // 0x3
+    field public static final int STATE_TOUCH_EXPLORING = 2; // 0x2
+    field public static final int STATE_TOUCH_INTERACTING = 1; // 0x1
+  }
+
+  public static interface TouchInteractionController.Callback {
+    method public void onMotionEvent(@NonNull android.view.MotionEvent);
+    method public void onStateChanged(int);
+  }
+
 }
 
 package android.accounts {
@@ -3918,6 +4003,7 @@
     method public void dump(@NonNull String, @Nullable java.io.FileDescriptor, @NonNull java.io.PrintWriter, @Nullable String[]);
     method @Deprecated public void enterPictureInPictureMode();
     method public boolean enterPictureInPictureMode(@NonNull android.app.PictureInPictureParams);
+    method public <T extends android.view.View> T findViewById(@IdRes int);
     method public void finish();
     method public void finishActivity(int);
     method @Deprecated public void finishActivityFromChild(@NonNull android.app.Activity, int);
@@ -4081,6 +4167,7 @@
     method public final void requestShowKeyboardShortcuts();
     method @Deprecated public boolean requestVisibleBehind(boolean);
     method public final boolean requestWindowFeature(int);
+    method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int);
     method public final void runOnUiThread(Runnable);
     method public void setActionBar(@Nullable android.widget.Toolbar);
     method public void setContentTransitionManager(android.transition.TransitionManager);
@@ -4367,6 +4454,7 @@
     method @Nullable public android.graphics.Rect getLaunchBounds();
     method public int getLaunchDisplayId();
     method public boolean getLockTaskMode();
+    method public boolean isPendingIntentBackgroundActivityLaunchAllowed();
     method public static android.app.ActivityOptions makeBasic();
     method public static android.app.ActivityOptions makeClipRevealAnimation(android.view.View, int, int, int, int);
     method public static android.app.ActivityOptions makeCustomAnimation(android.content.Context, int, int);
@@ -4380,6 +4468,8 @@
     method public android.app.ActivityOptions setLaunchBounds(@Nullable android.graphics.Rect);
     method public android.app.ActivityOptions setLaunchDisplayId(int);
     method public android.app.ActivityOptions setLockTaskEnabled(boolean);
+    method public void setPendingIntentBackgroundActivityLaunchAllowed(boolean);
+    method @NonNull public android.app.ActivityOptions setSplashScreenStyle(int);
     method public android.os.Bundle toBundle();
     method public void update(android.app.ActivityOptions);
     field public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
@@ -5365,12 +5455,30 @@
 
   public final class GameManager {
     method public int getGameMode();
+    method public void setGameState(@NonNull android.app.GameState);
     field public static final int GAME_MODE_BATTERY = 3; // 0x3
     field public static final int GAME_MODE_PERFORMANCE = 2; // 0x2
     field public static final int GAME_MODE_STANDARD = 1; // 0x1
     field public static final int GAME_MODE_UNSUPPORTED = 0; // 0x0
   }
 
+  public final class GameState implements android.os.Parcelable {
+    ctor public GameState(boolean, int);
+    ctor public GameState(boolean, int, @Nullable String, @NonNull android.os.Bundle);
+    method public int describeContents();
+    method @Nullable public String getDescription();
+    method @NonNull public android.os.Bundle getMetadata();
+    method public int getMode();
+    method public boolean isLoading();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.GameState> CREATOR;
+    field public static final int MODE_CONTENT = 4; // 0x4
+    field public static final int MODE_GAMEPLAY_INTERRUPTIBLE = 2; // 0x2
+    field public static final int MODE_GAMEPLAY_UNINTERRUPTIBLE = 3; // 0x3
+    field public static final int MODE_NONE = 1; // 0x1
+    field public static final int MODE_UNKNOWN = 0; // 0x0
+  }
+
   public class Instrumentation {
     ctor public Instrumentation();
     method public android.os.TestLooperManager acquireLooperManager(android.os.Looper);
@@ -5583,6 +5691,11 @@
     method @Deprecated public android.view.Window startActivity(String, android.content.Intent);
   }
 
+  public class LocaleManager {
+    method @NonNull public android.os.LocaleList getApplicationLocales();
+    method public void setApplicationLocales(@NonNull android.os.LocaleList);
+  }
+
   public class MediaRouteActionProvider extends android.view.ActionProvider {
     ctor public MediaRouteActionProvider(android.content.Context);
     method public android.view.View onCreateActionView();
@@ -6168,10 +6281,12 @@
     method public long[] getVibrationPattern();
     method public boolean hasUserSetImportance();
     method public boolean hasUserSetSound();
+    method public boolean isBlockable();
     method public boolean isConversation();
     method public boolean isDemoted();
     method public boolean isImportantConversation();
     method public void setAllowBubbles(boolean);
+    method public void setBlockable(boolean);
     method public void setBypassDnd(boolean);
     method public void setConversationId(@NonNull String, @NonNull String);
     method public void setDescription(String);
@@ -6244,6 +6359,7 @@
     method public android.app.NotificationManager.Policy getNotificationPolicy();
     method public boolean isNotificationListenerAccessGranted(android.content.ComponentName);
     method public boolean isNotificationPolicyAccessGranted();
+    method @WorkerThread public boolean matchesCallFilter(@NonNull android.net.Uri);
     method public void notify(int, android.app.Notification);
     method public void notify(String, int, android.app.Notification);
     method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification);
@@ -6650,7 +6766,7 @@
     method public boolean onUnbind(android.content.Intent);
     method public final void startForeground(int, android.app.Notification);
     method public final void startForeground(int, @NonNull android.app.Notification, int);
-    method public final void stopForeground(boolean);
+    method @Deprecated public final void stopForeground(boolean);
     method public final void stopForeground(int);
     method public final void stopSelf();
     method public final void stopSelf(int);
@@ -6663,6 +6779,7 @@
     field public static final int START_STICKY = 1; // 0x1
     field public static final int START_STICKY_COMPATIBILITY = 0; // 0x0
     field public static final int STOP_FOREGROUND_DETACH = 2; // 0x2
+    field @Deprecated public static final int STOP_FOREGROUND_LEGACY = 0; // 0x0
     field public static final int STOP_FOREGROUND_REMOVE = 1; // 0x1
   }
 
@@ -6685,6 +6802,16 @@
   }
 
   public class StatusBarManager {
+    method public void requestAddTileService(@NonNull android.content.ComponentName, @NonNull CharSequence, @NonNull android.graphics.drawable.Icon, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+    field public static final int TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND = 1004; // 0x3ec
+    field public static final int TILE_ADD_REQUEST_ERROR_BAD_COMPONENT = 1002; // 0x3ea
+    field public static final int TILE_ADD_REQUEST_ERROR_MISMATCHED_PACKAGE = 1000; // 0x3e8
+    field public static final int TILE_ADD_REQUEST_ERROR_NOT_CURRENT_USER = 1003; // 0x3eb
+    field public static final int TILE_ADD_REQUEST_ERROR_NO_STATUS_BAR_SERVICE = 1005; // 0x3ed
+    field public static final int TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS = 1001; // 0x3e9
+    field public static final int TILE_ADD_REQUEST_RESULT_TILE_ADDED = 2; // 0x2
+    field public static final int TILE_ADD_REQUEST_RESULT_TILE_ALREADY_ADDED = 1; // 0x1
+    field public static final int TILE_ADD_REQUEST_RESULT_TILE_NOT_ADDED = 0; // 0x0
   }
 
   public final class SyncNotedAppOp implements android.os.Parcelable {
@@ -6705,6 +6832,7 @@
   }
 
   public class TaskInfo {
+    method public boolean isVisible();
     field @Nullable public android.content.ComponentName baseActivity;
     field @NonNull public android.content.Intent baseIntent;
     field public boolean isRunning;
@@ -6746,7 +6874,7 @@
   public final class UiAutomation {
     method public void adoptShellPermissionIdentity();
     method public void adoptShellPermissionIdentity(@Nullable java.lang.String...);
-    method public void clearWindowAnimationFrameStats();
+    method @Deprecated public void clearWindowAnimationFrameStats();
     method public boolean clearWindowContentFrameStats(int);
     method public void dropShellPermissionIdentity();
     method public android.view.accessibility.AccessibilityEvent executeAndWaitForEvent(Runnable, android.app.UiAutomation.AccessibilityEventFilter, long) throws java.util.concurrent.TimeoutException;
@@ -6755,7 +6883,7 @@
     method public android.view.accessibility.AccessibilityNodeInfo findFocus(int);
     method public android.view.accessibility.AccessibilityNodeInfo getRootInActiveWindow();
     method public android.accessibilityservice.AccessibilityServiceInfo getServiceInfo();
-    method public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
+    method @Deprecated public android.view.WindowAnimationFrameStats getWindowAnimationFrameStats();
     method public android.view.WindowContentFrameStats getWindowContentFrameStats(int);
     method public java.util.List<android.view.accessibility.AccessibilityWindowInfo> getWindows();
     method @NonNull public android.util.SparseArray<java.util.List<android.view.accessibility.AccessibilityWindowInfo>> getWindowsOnAllDisplays();
@@ -6920,6 +7048,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public android.graphics.drawable.Drawable loadThumbnail(android.content.pm.PackageManager);
+    method public boolean shouldUseDefaultUnfoldTransition();
     method public boolean supportsMultipleDisplays();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.WallpaperInfo> CREATOR;
@@ -6978,7 +7107,7 @@
   }
 
   public static interface WallpaperManager.OnColorsChangedListener {
-    method public void onColorsChanged(android.app.WallpaperColors, int);
+    method public void onColorsChanged(@Nullable android.app.WallpaperColors, int);
   }
 
   public interface ZygotePreload {
@@ -7155,8 +7284,8 @@
     method public int getMaximumFailedPasswordsForWipe(@Nullable android.content.ComponentName);
     method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
-    method public int getNearbyAppStreamingPolicy();
-    method public int getNearbyNotificationStreamingPolicy();
+    method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy();
+    method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy();
     method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
     method @Nullable public CharSequence getOrganizationName(@NonNull android.content.ComponentName);
     method public java.util.List<android.telephony.data.ApnSetting> getOverrideApns(@NonNull android.content.ComponentName);
@@ -7453,7 +7582,7 @@
     field public static final int KEYGUARD_DISABLE_FEATURES_NONE = 0; // 0x0
     field public static final int KEYGUARD_DISABLE_FINGERPRINT = 32; // 0x20
     field public static final int KEYGUARD_DISABLE_IRIS = 256; // 0x100
-    field public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
+    field @Deprecated public static final int KEYGUARD_DISABLE_REMOTE_INPUT = 64; // 0x40
     field public static final int KEYGUARD_DISABLE_SECURE_CAMERA = 2; // 0x2
     field public static final int KEYGUARD_DISABLE_SECURE_NOTIFICATIONS = 4; // 0x4
     field public static final int KEYGUARD_DISABLE_TRUST_AGENTS = 16; // 0x10
@@ -7935,7 +8064,9 @@
     method public static final long getMinFlexMillis();
     method public long getMinLatencyMillis();
     method public static final long getMinPeriodMillis();
+    method public long getMinimumNetworkChunkBytes();
     method @Deprecated public int getNetworkType();
+    method public int getPriority();
     method @Nullable public android.net.NetworkRequest getRequiredNetwork();
     method @NonNull public android.content.ComponentName getService();
     method @NonNull public android.os.Bundle getTransientExtras();
@@ -7964,6 +8095,11 @@
     field public static final int NETWORK_TYPE_NONE = 0; // 0x0
     field public static final int NETWORK_TYPE_NOT_ROAMING = 3; // 0x3
     field public static final int NETWORK_TYPE_UNMETERED = 2; // 0x2
+    field public static final int PRIORITY_DEFAULT = 300; // 0x12c
+    field public static final int PRIORITY_HIGH = 400; // 0x190
+    field public static final int PRIORITY_LOW = 200; // 0xc8
+    field public static final int PRIORITY_MAX = 500; // 0x1f4
+    field public static final int PRIORITY_MIN = 100; // 0x64
   }
 
   public static final class JobInfo.Builder {
@@ -7977,11 +8113,13 @@
     method public android.app.job.JobInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @Deprecated public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
+    method @NonNull public android.app.job.JobInfo.Builder setMinimumNetworkChunkBytes(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long, long);
     method @RequiresPermission(android.Manifest.permission.RECEIVE_BOOT_COMPLETED) public android.app.job.JobInfo.Builder setPersisted(boolean);
     method public android.app.job.JobInfo.Builder setPrefetch(boolean);
+    method @NonNull public android.app.job.JobInfo.Builder setPriority(int);
     method public android.app.job.JobInfo.Builder setRequiredNetwork(@Nullable android.net.NetworkRequest);
     method public android.app.job.JobInfo.Builder setRequiredNetworkType(int);
     method public android.app.job.JobInfo.Builder setRequiresBatteryNotLow(boolean);
@@ -8029,6 +8167,7 @@
     field public static final int STOP_REASON_CONSTRAINT_DEVICE_IDLE = 8; // 0x8
     field public static final int STOP_REASON_CONSTRAINT_STORAGE_NOT_LOW = 9; // 0x9
     field public static final int STOP_REASON_DEVICE_STATE = 4; // 0x4
+    field public static final int STOP_REASON_ESTIMATED_APP_LAUNCH_TIME_CHANGED = 15; // 0xf
     field public static final int STOP_REASON_PREEMPT = 2; // 0x2
     field public static final int STOP_REASON_QUOTA = 10; // 0xa
     field public static final int STOP_REASON_SYSTEM_PROCESSING = 14; // 0xe
@@ -8069,11 +8208,13 @@
   public final class JobWorkItem implements android.os.Parcelable {
     ctor public JobWorkItem(android.content.Intent);
     ctor public JobWorkItem(android.content.Intent, long, long);
+    ctor public JobWorkItem(@Nullable android.content.Intent, long, long, long);
     method public int describeContents();
     method public int getDeliveryCount();
     method public long getEstimatedNetworkDownloadBytes();
     method public long getEstimatedNetworkUploadBytes();
     method public android.content.Intent getIntent();
+    method public long getMinimumNetworkChunkBytes();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.app.job.JobWorkItem> CREATOR;
   }
@@ -8637,6 +8778,7 @@
     method public android.bluetooth.le.BluetoothLeScanner getBluetoothLeScanner();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.Set<android.bluetooth.BluetoothDevice> getBondedDevices();
     method @Deprecated public static android.bluetooth.BluetoothAdapter getDefaultAdapter();
+    method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public java.time.Duration getDiscoverableTimeout();
     method public int getLeMaximumAdvertisingDataLength();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public String getName();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getProfileConnectionState(int);
@@ -8648,9 +8790,11 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN) public boolean isDiscovering();
     method public boolean isEnabled();
     method public boolean isLe2MPhySupported();
+    method public int isLeAudioSupported();
     method public boolean isLeCodedPhySupported();
     method public boolean isLeExtendedAdvertisingSupported();
     method public boolean isLePeriodicAdvertisingSupported();
+    method public int isLePeriodicAdvertisingSyncTransferSenderSupported();
     method public boolean isMultipleAdvertisementSupported();
     method public boolean isOffloadedFilteringSupported();
     method public boolean isOffloadedScanBatchingSupported();
@@ -8929,11 +9073,15 @@
 
   public final class BluetoothClass implements android.os.Parcelable {
     method public int describeContents();
+    method public boolean doesClassMatch(int);
     method public int getDeviceClass();
     method public int getMajorDeviceClass();
     method public boolean hasService(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothClass> CREATOR;
+    field public static final int PROFILE_A2DP = 1; // 0x1
+    field public static final int PROFILE_HEADSET = 0; // 0x0
+    field public static final int PROFILE_HID = 3; // 0x3
   }
 
   public static class BluetoothClass.Device {
@@ -9018,6 +9166,81 @@
     field public static final int TELEPHONY = 4194304; // 0x400000
   }
 
+  public final class BluetoothCodecConfig implements android.os.Parcelable {
+    ctor public BluetoothCodecConfig(int);
+    method public int describeContents();
+    method public int getBitsPerSample();
+    method public int getChannelMode();
+    method public int getCodecPriority();
+    method public long getCodecSpecific1();
+    method public long getCodecSpecific2();
+    method public long getCodecSpecific3();
+    method public long getCodecSpecific4();
+    method public int getCodecType();
+    method public static int getMaxCodecType();
+    method public int getSampleRate();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BITS_PER_SAMPLE_16 = 1; // 0x1
+    field public static final int BITS_PER_SAMPLE_24 = 2; // 0x2
+    field public static final int BITS_PER_SAMPLE_32 = 4; // 0x4
+    field public static final int BITS_PER_SAMPLE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_MONO = 1; // 0x1
+    field public static final int CHANNEL_MODE_NONE = 0; // 0x0
+    field public static final int CHANNEL_MODE_STEREO = 2; // 0x2
+    field public static final int CODEC_PRIORITY_DEFAULT = 0; // 0x0
+    field public static final int CODEC_PRIORITY_DISABLED = -1; // 0xffffffff
+    field public static final int CODEC_PRIORITY_HIGHEST = 1000000; // 0xf4240
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecConfig> CREATOR;
+    field public static final int SAMPLE_RATE_176400 = 16; // 0x10
+    field public static final int SAMPLE_RATE_192000 = 32; // 0x20
+    field public static final int SAMPLE_RATE_44100 = 1; // 0x1
+    field public static final int SAMPLE_RATE_48000 = 2; // 0x2
+    field public static final int SAMPLE_RATE_88200 = 4; // 0x4
+    field public static final int SAMPLE_RATE_96000 = 8; // 0x8
+    field public static final int SAMPLE_RATE_NONE = 0; // 0x0
+    field public static final int SOURCE_CODEC_TYPE_AAC = 1; // 0x1
+    field public static final int SOURCE_CODEC_TYPE_APTX = 2; // 0x2
+    field public static final int SOURCE_CODEC_TYPE_APTX_HD = 3; // 0x3
+    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LDAC = 4; // 0x4
+    field public static final int SOURCE_CODEC_TYPE_SBC = 0; // 0x0
+  }
+
+  public static final class BluetoothCodecConfig.Builder {
+    ctor public BluetoothCodecConfig.Builder();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setBitsPerSample(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setChannelMode(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecPriority(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific1(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific2(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific3(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecSpecific4(long);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setCodecType(int);
+    method @NonNull public android.bluetooth.BluetoothCodecConfig.Builder setSampleRate(int);
+  }
+
+  public final class BluetoothCodecStatus implements android.os.Parcelable {
+    ctor public BluetoothCodecStatus(@Nullable android.bluetooth.BluetoothCodecConfig, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>, @Nullable java.util.List<android.bluetooth.BluetoothCodecConfig>);
+    method public int describeContents();
+    method @Nullable public android.bluetooth.BluetoothCodecConfig getCodecConfig();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsLocalCapabilities();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothCodecConfig> getCodecsSelectableCapabilities();
+    method public boolean isCodecConfigSelectable(@Nullable android.bluetooth.BluetoothCodecConfig);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothCodecStatus> CREATOR;
+    field public static final String EXTRA_CODEC_STATUS = "android.bluetooth.extra.CODEC_STATUS";
+  }
+
+  public final class BluetoothCsipSetCoordinator implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
+    method public void close();
+    method protected void finalize();
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+    method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED = "android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
+  }
+
   public final class BluetoothDevice implements android.os.Parcelable {
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public android.bluetooth.BluetoothGatt connectGatt(android.content.Context, boolean, android.bluetooth.BluetoothGattCallback, int);
@@ -9065,6 +9288,7 @@
     field public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
     field public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
     field public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
+    field public static final String EXTRA_IS_COORDINATED_SET_MEMBER = "android.bluetooth.extra.IS_COORDINATED_SET_MEMBER";
     field public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
     field public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
     field public static final String EXTRA_PAIRING_VARIANT = "android.bluetooth.device.extra.PAIRING_VARIANT";
@@ -9096,10 +9320,10 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void disconnect();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean discoverServices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean executeReliableWrite();
-    method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
-    method public int getConnectionState(android.bluetooth.BluetoothDevice);
+    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+    method @Deprecated public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public android.bluetooth.BluetoothDevice getDevice();
-    method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+    method @Deprecated public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean readCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
@@ -9110,14 +9334,17 @@
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean requestMtu(int);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic, boolean);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void setPreferredPhy(int, int, int);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeCharacteristic(@NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean writeDescriptor(android.bluetooth.BluetoothGattDescriptor);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int writeDescriptor(@NonNull android.bluetooth.BluetoothGattDescriptor, @NonNull byte[]);
     field public static final int CONNECTION_PRIORITY_BALANCED = 0; // 0x0
     field public static final int CONNECTION_PRIORITY_HIGH = 1; // 0x1
     field public static final int CONNECTION_PRIORITY_LOW_POWER = 2; // 0x2
     field public static final int GATT_CONNECTION_CONGESTED = 143; // 0x8f
     field public static final int GATT_FAILURE = 257; // 0x101
     field public static final int GATT_INSUFFICIENT_AUTHENTICATION = 5; // 0x5
+    field public static final int GATT_INSUFFICIENT_AUTHORIZATION = 8; // 0x8
     field public static final int GATT_INSUFFICIENT_ENCRYPTION = 15; // 0xf
     field public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 13; // 0xd
     field public static final int GATT_INVALID_OFFSET = 7; // 0x7
@@ -9129,11 +9356,14 @@
 
   public abstract class BluetoothGattCallback {
     ctor public BluetoothGattCallback();
-    method public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
-    method public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
+    method @Deprecated public void onCharacteristicChanged(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic);
+    method public void onCharacteristicChanged(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[]);
+    method @Deprecated public void onCharacteristicRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
+    method public void onCharacteristicRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattCharacteristic, @NonNull byte[], int);
     method public void onCharacteristicWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattCharacteristic, int);
     method public void onConnectionStateChange(android.bluetooth.BluetoothGatt, int, int);
-    method public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
+    method @Deprecated public void onDescriptorRead(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
+    method public void onDescriptorRead(@NonNull android.bluetooth.BluetoothGatt, @NonNull android.bluetooth.BluetoothGattDescriptor, int, @NonNull byte[]);
     method public void onDescriptorWrite(android.bluetooth.BluetoothGatt, android.bluetooth.BluetoothGattDescriptor, int);
     method public void onMtuChanged(android.bluetooth.BluetoothGatt, int, int);
     method public void onPhyRead(android.bluetooth.BluetoothGatt, int, int, int);
@@ -9150,20 +9380,20 @@
     method public int describeContents();
     method public android.bluetooth.BluetoothGattDescriptor getDescriptor(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattDescriptor> getDescriptors();
-    method public Float getFloatValue(int, int);
+    method @Deprecated public Float getFloatValue(int, int);
     method public int getInstanceId();
-    method public Integer getIntValue(int, int);
+    method @Deprecated public Integer getIntValue(int, int);
     method public int getPermissions();
     method public int getProperties();
     method public android.bluetooth.BluetoothGattService getService();
-    method public String getStringValue(int);
+    method @Deprecated public String getStringValue(int);
     method public java.util.UUID getUuid();
-    method public byte[] getValue();
+    method @Deprecated public byte[] getValue();
     method public int getWriteType();
-    method public boolean setValue(byte[]);
-    method public boolean setValue(int, int, int);
-    method public boolean setValue(int, int, int, int);
-    method public boolean setValue(String);
+    method @Deprecated public boolean setValue(byte[]);
+    method @Deprecated public boolean setValue(int, int, int);
+    method @Deprecated public boolean setValue(int, int, int, int);
+    method @Deprecated public boolean setValue(String);
     method public void setWriteType(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattCharacteristic> CREATOR;
@@ -9203,8 +9433,8 @@
     method public android.bluetooth.BluetoothGattCharacteristic getCharacteristic();
     method public int getPermissions();
     method public java.util.UUID getUuid();
-    method public byte[] getValue();
-    method public boolean setValue(byte[]);
+    method @Deprecated public byte[] getValue();
+    method @Deprecated public boolean setValue(byte[]);
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothGattDescriptor> CREATOR;
     field public static final byte[] DISABLE_NOTIFICATION_VALUE;
@@ -9231,7 +9461,8 @@
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     method public android.bluetooth.BluetoothGattService getService(java.util.UUID);
     method public java.util.List<android.bluetooth.BluetoothGattService> getServices();
-    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean notifyCharacteristicChanged(android.bluetooth.BluetoothDevice, android.bluetooth.BluetoothGattCharacteristic, boolean);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int notifyCharacteristicChanged(@NonNull android.bluetooth.BluetoothDevice, @NonNull android.bluetooth.BluetoothGattCharacteristic, boolean, @NonNull byte[]);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public void readPhy(android.bluetooth.BluetoothDevice);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean removeService(android.bluetooth.BluetoothGattService);
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public boolean sendResponse(android.bluetooth.BluetoothDevice, int, int, int, byte[]);
@@ -9427,9 +9658,24 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(@NonNull android.bluetooth.BluetoothDevice);
     method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getGroupId(@NonNull android.bluetooth.BluetoothDevice);
     field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED = "android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED";
   }
 
+  public final class BluetoothLeAudioCodecConfig {
+    method @NonNull public String getCodecName();
+    method public int getCodecType();
+    method public static int getMaxCodecType();
+    field public static final int SOURCE_CODEC_TYPE_INVALID = 1000000; // 0xf4240
+    field public static final int SOURCE_CODEC_TYPE_LC3 = 0; // 0x0
+  }
+
+  public static final class BluetoothLeAudioCodecConfig.Builder {
+    ctor public BluetoothLeAudioCodecConfig.Builder();
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig build();
+    method @NonNull public android.bluetooth.BluetoothLeAudioCodecConfig.Builder setCodecType(int);
+  }
+
   public final class BluetoothManager {
     method public android.bluetooth.BluetoothAdapter getAdapter();
     method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(int);
@@ -9443,6 +9689,7 @@
     method public int getConnectionState(android.bluetooth.BluetoothDevice);
     method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
     field public static final int A2DP = 2; // 0x2
+    field public static final int CSIP_SET_COORDINATOR = 25; // 0x19
     field public static final String EXTRA_PREVIOUS_STATE = "android.bluetooth.profile.extra.PREVIOUS_STATE";
     field public static final String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
     field public static final int GATT = 7; // 0x7
@@ -9451,6 +9698,7 @@
     field @Deprecated public static final int HEALTH = 3; // 0x3
     field public static final int HEARING_AID = 21; // 0x15
     field public static final int HID_DEVICE = 19; // 0x13
+    field public static final int LE_AUDIO = 22; // 0x16
     field public static final int SAP = 10; // 0xa
     field public static final int STATE_CONNECTED = 2; // 0x2
     field public static final int STATE_CONNECTING = 1; // 0x1
@@ -9489,7 +9737,12 @@
     field public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2; // 0x2
     field public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1; // 0x1
     field public static final int ERROR_DEVICE_NOT_BONDED = 3; // 0x3
+    field public static final int ERROR_FEATURE_NOT_SUPPORTED = 10; // 0xa
+    field public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101; // 0x65
+    field public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102; // 0x66
     field public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6; // 0x6
+    field public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8; // 0x8
+    field public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9; // 0x9
     field public static final int ERROR_UNKNOWN = 2147483647; // 0x7fffffff
     field public static final int SUCCESS = 0; // 0x0
   }
@@ -9517,6 +9770,7 @@
     method public java.util.Map<android.os.ParcelUuid,byte[]> getServiceData();
     method @NonNull public java.util.List<android.os.ParcelUuid> getServiceSolicitationUuids();
     method public java.util.List<android.os.ParcelUuid> getServiceUuids();
+    method @NonNull public java.util.List<android.bluetooth.le.TransportDiscoveryData> getTransportDiscoveryData();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.AdvertiseData> CREATOR;
   }
@@ -9527,6 +9781,7 @@
     method public android.bluetooth.le.AdvertiseData.Builder addServiceData(android.os.ParcelUuid, byte[]);
     method @NonNull public android.bluetooth.le.AdvertiseData.Builder addServiceSolicitationUuid(@NonNull android.os.ParcelUuid);
     method public android.bluetooth.le.AdvertiseData.Builder addServiceUuid(android.os.ParcelUuid);
+    method @NonNull public android.bluetooth.le.AdvertiseData.Builder addTransportDiscoveryData(@NonNull android.bluetooth.le.TransportDiscoveryData);
     method public android.bluetooth.le.AdvertiseData build();
     method public android.bluetooth.le.AdvertiseData.Builder setIncludeDeviceName(boolean);
     method public android.bluetooth.le.AdvertiseData.Builder setIncludeTxPowerLevel(boolean);
@@ -9786,12 +10041,50 @@
     method public android.bluetooth.le.ScanSettings.Builder setScanMode(int);
   }
 
+  public final class TransportBlock implements android.os.Parcelable {
+    ctor public TransportBlock(int, int, int, @Nullable byte[]);
+    method public int describeContents();
+    method public int getOrgId();
+    method public int getTdsFlags();
+    method @Nullable public byte[] getTransportData();
+    method public int getTransportDataLength();
+    method @Nullable public byte[] toByteArray();
+    method public int totalBytes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportBlock> CREATOR;
+  }
+
+  public final class TransportDiscoveryData implements android.os.Parcelable {
+    ctor public TransportDiscoveryData(int, @NonNull java.util.List<android.bluetooth.le.TransportBlock>);
+    ctor public TransportDiscoveryData(@NonNull byte[]);
+    method public int describeContents();
+    method @NonNull public java.util.List<android.bluetooth.le.TransportBlock> getTransportBlocks();
+    method public int getTransportDataType();
+    method @Nullable public byte[] toByteArray();
+    method public int totalBytes();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.le.TransportDiscoveryData> CREATOR;
+  }
+
 }
 
 package android.companion {
 
+  public final class AssociationInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.net.MacAddress getDeviceMacAddress();
+    method @Nullable public String getDeviceProfile();
+    method @Nullable public CharSequence getDisplayName();
+    method public int getId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationInfo> CREATOR;
+  }
+
   public final class AssociationRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @Nullable public String getDeviceProfile();
+    method @Nullable public CharSequence getDisplayName();
+    method public boolean isSingleDevice();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociationRequest> CREATOR;
     field public static final String DEVICE_PROFILE_WATCH = "android.app.role.COMPANION_DEVICE_WATCH";
@@ -9802,6 +10095,7 @@
     method @NonNull public android.companion.AssociationRequest.Builder addDeviceFilter(@Nullable android.companion.DeviceFilter<?>);
     method @NonNull public android.companion.AssociationRequest build();
     method @NonNull public android.companion.AssociationRequest.Builder setDeviceProfile(@NonNull String);
+    method @NonNull public android.companion.AssociationRequest.Builder setDisplayName(@NonNull CharSequence);
     method @NonNull public android.companion.AssociationRequest.Builder setSingleDevice(boolean);
   }
 
@@ -9837,27 +10131,37 @@
   }
 
   public final class CompanionDeviceManager {
-    method @RequiresPermission(value=android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
-    method public void disassociate(@NonNull String);
-    method @NonNull public java.util.List<java.lang.String> getAssociations();
-    method public boolean hasNotificationAccess(android.content.ComponentName);
+    method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull android.companion.CompanionDeviceManager.Callback, @Nullable android.os.Handler);
+    method @RequiresPermission(anyOf={android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH, "android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING", "android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"}, conditional=true) public void associate(@NonNull android.companion.AssociationRequest, @NonNull java.util.concurrent.Executor, @NonNull android.companion.CompanionDeviceManager.Callback);
+    method @Deprecated public void disassociate(@NonNull String);
+    method public void disassociate(int);
+    method @Deprecated @NonNull public java.util.List<java.lang.String> getAssociations();
+    method @NonNull public java.util.List<android.companion.AssociationInfo> getMyAssociations();
+    method @Deprecated public boolean hasNotificationAccess(android.content.ComponentName);
     method public void requestNotificationAccess(android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void startObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
     method @RequiresPermission(android.Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) public void stopObservingDevicePresence(@NonNull String) throws android.companion.DeviceNotAssociatedException;
-    field public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
+    field public static final String EXTRA_ASSOCIATION = "android.companion.extra.ASSOCIATION";
+    field @Deprecated public static final String EXTRA_DEVICE = "android.companion.extra.DEVICE";
   }
 
   public abstract static class CompanionDeviceManager.Callback {
     ctor public CompanionDeviceManager.Callback();
-    method public abstract void onDeviceFound(android.content.IntentSender);
-    method public abstract void onFailure(CharSequence);
+    method public void onAssociationCreated(@NonNull android.companion.AssociationInfo);
+    method public void onAssociationPending(@NonNull android.content.IntentSender);
+    method @Deprecated public void onDeviceFound(@NonNull android.content.IntentSender);
+    method public abstract void onFailure(@Nullable CharSequence);
   }
 
   public abstract class CompanionDeviceService extends android.app.Service {
     ctor public CompanionDeviceService();
+    method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void dispatchMessage(int, int, @NonNull byte[]);
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
-    method @MainThread public abstract void onDeviceAppeared(@NonNull String);
-    method @MainThread public abstract void onDeviceDisappeared(@NonNull String);
+    method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String);
+    method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo);
+    method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String);
+    method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo);
+    method @MainThread public void onDispatchMessage(int, int, @NonNull byte[]);
     field public static final String SERVICE_INTERFACE = "android.companion.CompanionDeviceService";
   }
 
@@ -9953,12 +10257,14 @@
     method @Nullable public String getPackageName();
     method public int getUid();
     method public boolean isTrusted(@NonNull android.content.Context);
+    method @NonNull public static android.content.AttributionSource myAttributionSource();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.AttributionSource> CREATOR;
   }
 
   public static final class AttributionSource.Builder {
     ctor public AttributionSource.Builder(int);
+    ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource);
     method @NonNull public android.content.AttributionSource build();
     method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource);
@@ -10533,6 +10839,7 @@
     method @NonNull public final String getString(@StringRes int);
     method @NonNull public final String getString(@StringRes int, java.lang.Object...);
     method public abstract Object getSystemService(@NonNull String);
+    method public final <T> T getSystemService(@NonNull Class<T>);
     method @Nullable public abstract String getSystemServiceName(@NonNull Class<?>);
     method @NonNull public final CharSequence getText(@StringRes int);
     method public abstract android.content.res.Resources.Theme getTheme();
@@ -10648,6 +10955,7 @@
     field public static final String KEYGUARD_SERVICE = "keyguard";
     field public static final String LAUNCHER_APPS_SERVICE = "launcherapps";
     field @UiContext public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+    field public static final String LOCALE_SERVICE = "locale";
     field public static final String LOCATION_SERVICE = "location";
     field public static final String MEDIA_COMMUNICATION_SERVICE = "media_communication";
     field public static final String MEDIA_METRICS_SERVICE = "media_metrics";
@@ -10670,14 +10978,18 @@
     field public static final String PERFORMANCE_HINT_SERVICE = "performance_hint";
     field public static final String POWER_SERVICE = "power";
     field public static final String PRINT_SERVICE = "print";
+    field public static final int RECEIVER_EXPORTED = 2; // 0x2
+    field public static final int RECEIVER_NOT_EXPORTED = 4; // 0x4
     field public static final int RECEIVER_VISIBLE_TO_INSTANT_APPS = 1; // 0x1
     field public static final String RESTRICTIONS_SERVICE = "restrictions";
     field public static final String ROLE_SERVICE = "role";
     field public static final String SEARCH_SERVICE = "search";
     field public static final String SENSOR_SERVICE = "sensor";
     field public static final String SHORTCUT_SERVICE = "shortcut";
+    field public static final String STATUS_BAR_SERVICE = "statusbar";
     field public static final String STORAGE_SERVICE = "storage";
     field public static final String STORAGE_STATS_SERVICE = "storagestats";
+    field public static final String SUPPLEMENTAL_PROCESS_SERVICE = "supplemental_process";
     field public static final String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final String TELECOM_SERVICE = "telecom";
     field public static final String TELEPHONY_IMS_SERVICE = "telephony_ims";
@@ -10685,6 +10997,7 @@
     field public static final String TELEPHONY_SUBSCRIPTION_SERVICE = "telephony_subscription_service";
     field public static final String TEXT_CLASSIFICATION_SERVICE = "textclassification";
     field public static final String TEXT_SERVICES_MANAGER_SERVICE = "textservices";
+    field public static final String TV_IAPP_SERVICE = "tv_iapp";
     field public static final String TV_INPUT_SERVICE = "tv_input";
     field public static final String UI_MODE_SERVICE = "uimode";
     field public static final String USAGE_STATS_SERVICE = "usagestats";
@@ -11030,6 +11343,7 @@
     field public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE";
     field public static final String ACTION_ALL_APPS = "android.intent.action.ALL_APPS";
     field public static final String ACTION_ANSWER = "android.intent.action.ANSWER";
+    field public static final String ACTION_APPLICATION_LOCALE_CHANGED = "android.intent.action.APPLICATION_LOCALE_CHANGED";
     field public static final String ACTION_APPLICATION_PREFERENCES = "android.intent.action.APPLICATION_PREFERENCES";
     field public static final String ACTION_APPLICATION_RESTRICTIONS_CHANGED = "android.intent.action.APPLICATION_RESTRICTIONS_CHANGED";
     field public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR";
@@ -11132,6 +11446,7 @@
     field public static final String ACTION_QUICK_VIEW = "android.intent.action.QUICK_VIEW";
     field public static final String ACTION_REBOOT = "android.intent.action.REBOOT";
     field public static final String ACTION_RUN = "android.intent.action.RUN";
+    field public static final String ACTION_SAFETY_CENTER = "android.intent.action.SAFETY_CENTER";
     field public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
     field public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON";
     field public static final String ACTION_SEARCH = "android.intent.action.SEARCH";
@@ -11161,6 +11476,7 @@
     field public static final String ACTION_VIEW_LOCUS = "android.intent.action.VIEW_LOCUS";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE = "android.intent.action.VIEW_PERMISSION_USAGE";
     field @RequiresPermission(android.Manifest.permission.START_VIEW_PERMISSION_USAGE) public static final String ACTION_VIEW_PERMISSION_USAGE_FOR_PERIOD = "android.intent.action.VIEW_PERMISSION_USAGE_FOR_PERIOD";
+    field @RequiresPermission("android.permission.MANAGE_SENSOR_PRIVACY") public static final String ACTION_VIEW_SAFETY_HUB = "android.intent.action.VIEW_SAFETY_HUB";
     field public static final String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND";
     field @Deprecated public static final String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED";
     field public static final String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH";
@@ -11172,11 +11488,13 @@
     field public static final String CATEGORY_APP_CONTACTS = "android.intent.category.APP_CONTACTS";
     field public static final String CATEGORY_APP_EMAIL = "android.intent.category.APP_EMAIL";
     field public static final String CATEGORY_APP_FILES = "android.intent.category.APP_FILES";
+    field public static final String CATEGORY_APP_FITNESS = "android.intent.category.APP_FITNESS";
     field public static final String CATEGORY_APP_GALLERY = "android.intent.category.APP_GALLERY";
     field public static final String CATEGORY_APP_MAPS = "android.intent.category.APP_MAPS";
     field public static final String CATEGORY_APP_MARKET = "android.intent.category.APP_MARKET";
     field public static final String CATEGORY_APP_MESSAGING = "android.intent.category.APP_MESSAGING";
     field public static final String CATEGORY_APP_MUSIC = "android.intent.category.APP_MUSIC";
+    field public static final String CATEGORY_APP_WEATHER = "android.intent.category.APP_WEATHER";
     field public static final String CATEGORY_BROWSABLE = "android.intent.category.BROWSABLE";
     field public static final String CATEGORY_CAR_DOCK = "android.intent.category.CAR_DOCK";
     field public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
@@ -11248,6 +11566,7 @@
     field public static final String EXTRA_INSTALLER_PACKAGE_NAME = "android.intent.extra.INSTALLER_PACKAGE_NAME";
     field public static final String EXTRA_INTENT = "android.intent.extra.INTENT";
     field public static final String EXTRA_KEY_EVENT = "android.intent.extra.KEY_EVENT";
+    field public static final String EXTRA_LOCALE_LIST = "android.intent.extra.LOCALE_LIST";
     field public static final String EXTRA_LOCAL_ONLY = "android.intent.extra.LOCAL_ONLY";
     field public static final String EXTRA_LOCUS_ID = "android.intent.extra.LOCUS_ID";
     field public static final String EXTRA_MIME_TYPES = "android.intent.extra.MIME_TYPES";
@@ -11256,6 +11575,7 @@
     field public static final String EXTRA_PACKAGE_NAME = "android.intent.extra.PACKAGE_NAME";
     field public static final String EXTRA_PERMISSION_GROUP_NAME = "android.intent.extra.PERMISSION_GROUP_NAME";
     field public static final String EXTRA_PHONE_NUMBER = "android.intent.extra.PHONE_NUMBER";
+    field public static final String EXTRA_PREVIOUS_UID = "android.intent.extra.PREVIOUS_UID";
     field public static final String EXTRA_PROCESS_TEXT = "android.intent.extra.PROCESS_TEXT";
     field public static final String EXTRA_PROCESS_TEXT_READONLY = "android.intent.extra.PROCESS_TEXT_READONLY";
     field public static final String EXTRA_QUICK_VIEW_FEATURES = "android.intent.extra.QUICK_VIEW_FEATURES";
@@ -11287,6 +11607,7 @@
     field public static final String EXTRA_TIMEZONE = "time-zone";
     field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final String EXTRA_UID = "android.intent.extra.UID";
+    field public static final String EXTRA_UID_CHANGING = "android.intent.extra.UID_CHANGING";
     field public static final String EXTRA_USER = "android.intent.extra.USER";
     field public static final String EXTRA_USER_INITIATED = "android.intent.extra.USER_INITIATED";
     field public static final int FILL_IN_ACTION = 1; // 0x1
@@ -11369,6 +11690,8 @@
     method public final void addDataScheme(String);
     method public final void addDataSchemeSpecificPart(String, int);
     method public final void addDataType(String) throws android.content.IntentFilter.MalformedMimeTypeException;
+    method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicate();
+    method @NonNull public java.util.function.Predicate<android.content.Intent> asPredicateWithTypeResolution(@NonNull android.content.ContentResolver);
     method public final java.util.Iterator<android.content.IntentFilter.AuthorityEntry> authoritiesIterator();
     method public final java.util.Iterator<java.lang.String> categoriesIterator();
     method public final int countActions();
@@ -12380,6 +12703,7 @@
     method @NonNull public java.io.OutputStream openWrite(@NonNull String, long, long) throws java.io.IOException;
     method public void removeChildSessionId(int);
     method public void removeSplit(@NonNull String) throws java.io.IOException;
+    method public void requestChecksums(@NonNull String, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull java.util.concurrent.Executor, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, java.io.FileNotFoundException;
     method @Deprecated public void setChecksums(@NonNull String, @NonNull java.util.List<android.content.pm.Checksum>, @Nullable byte[]) throws java.io.IOException;
     method public void setStagingProgress(float);
     method public void transfer(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12501,6 +12825,7 @@
     method public abstract boolean addPermissionAsync(@NonNull android.content.pm.PermissionInfo);
     method @Deprecated public abstract void addPreferredActivity(@NonNull android.content.IntentFilter, int, @Nullable android.content.ComponentName[], @NonNull android.content.ComponentName);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean addWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
+    method public boolean canPackageQuery(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract boolean canRequestPackageInstalls();
     method public abstract String[] canonicalToCurrentPackageNames(@NonNull String[]);
     method @CheckResult public abstract int checkPermission(@NonNull String, @NonNull String);
@@ -12514,7 +12839,8 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityBanner(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.graphics.drawable.Drawable getActivityIcon(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ActivityInfo getActivityInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.graphics.drawable.Drawable getActivityLogo(@NonNull android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract java.util.List<android.content.pm.PermissionGroupInfo> getAllPermissionGroups(int);
@@ -12523,7 +12849,8 @@
     method public abstract int getApplicationEnabledSetting(@NonNull String);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull android.content.pm.ApplicationInfo);
     method @NonNull public abstract android.graphics.drawable.Drawable getApplicationIcon(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ApplicationInfo getApplicationInfo(@NonNull String, @NonNull android.content.pm.PackageManager.ApplicationInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
     method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -12534,27 +12861,36 @@
     method @Nullable public abstract android.graphics.drawable.Drawable getDrawable(@NonNull String, @DrawableRes int, @Nullable android.content.pm.ApplicationInfo);
     method public void getGroupOfPlatformPermission(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.String>);
     method @NonNull public android.content.pm.InstallSourceInfo getInstallSourceInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
+    method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags);
     method @NonNull public java.util.List<android.content.pm.ModuleInfo> getInstalledModules(int);
-    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
+    method @NonNull public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Deprecated @Nullable public abstract String getInstallerPackageName(@NonNull String);
     method @NonNull public abstract byte[] getInstantAppCookie();
     method public abstract int getInstantAppCookieMaxBytes();
     method @NonNull public abstract android.content.pm.InstrumentationInfo getInstrumentationInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract android.content.Intent getLaunchIntentForPackage(@NonNull String);
+    method @NonNull public android.content.IntentSender getLaunchIntentSenderForPackage(@NonNull String);
     method @Nullable public abstract android.content.Intent getLeanbackLaunchIntentForPackage(@NonNull String);
     method @NonNull public java.util.Set<java.lang.String> getMimeGroup(@NonNull String);
     method @NonNull public android.content.pm.ModuleInfo getModuleInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String getNameForUid(int);
-    method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+    method @Deprecated @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, int);
+    method @Nullable public android.content.pm.PackageInfo getPackageArchiveInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method public abstract int[] getPackageGids(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract int[] getPackageGids(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Nullable public int[] getPackageGids(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.PackageInfo getPackageInfo(@NonNull android.content.pm.VersionedPackage, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.pm.PackageInstaller getPackageInstaller();
-    method public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated public abstract int getPackageUid(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public int getPackageUid(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @Nullable public abstract String[] getPackagesForUid(int);
-    method @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], int);
+    method @NonNull public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(@NonNull String[], @NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @NonNull public abstract android.content.pm.PermissionGroupInfo getPermissionGroupInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public abstract android.content.pm.PermissionInfo getPermissionInfo(@NonNull String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public void getPlatformPermissionsForGroup(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<java.lang.String>>);
@@ -12562,14 +12898,18 @@
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.pm.PackageManager.Property getProperty(@NonNull String, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ProviderInfo getProviderInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ActivityInfo getReceiverInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForActivity(@NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public android.content.res.Resources getResourcesForApplication(@NonNull android.content.pm.ApplicationInfo, @Nullable android.content.res.Configuration) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public abstract android.content.res.Resources getResourcesForApplication(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+    method @Deprecated @NonNull public abstract android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @NonNull public android.content.pm.ServiceInfo getServiceInfo(@NonNull android.content.ComponentName, @NonNull android.content.pm.PackageManager.ComponentInfoFlags) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
+    method @NonNull public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(@NonNull android.content.pm.PackageManager.PackageInfoFlags);
     method @Nullable public android.os.Bundle getSuspendedPackageAppExtras();
     method public boolean getSyntheticAppDetailsActivityEnabled(@NonNull String);
     method @NonNull public abstract android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
@@ -12597,13 +12937,19 @@
     method public abstract boolean isSafeMode();
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryActivityProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryApplicationProperty(@NonNull String);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, int);
+    method @NonNull public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(@Nullable String, int, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(@NonNull String, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
-    method @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable android.content.Intent[], @NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(@Nullable android.content.ComponentName, @Nullable java.util.List<android.content.Intent>, @NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, int);
+    method @NonNull public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method @NonNull public abstract java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(@Nullable String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryProviderProperty(@NonNull String);
     method @NonNull public java.util.List<android.content.pm.PackageManager.Property> queryReceiverProperty(@NonNull String);
@@ -12612,13 +12958,17 @@
     method public abstract void removePermission(@NonNull String);
     method @RequiresPermission(value="android.permission.WHITELIST_RESTRICTED_PERMISSIONS", conditional=true) public boolean removeWhitelistedRestrictedPermission(@NonNull String, @NonNull String, int);
     method public void requestChecksums(@NonNull String, boolean, int, @NonNull java.util.List<java.security.cert.Certificate>, @NonNull android.content.pm.PackageManager.OnChecksumsReadyListener) throws java.security.cert.CertificateEncodingException, android.content.pm.PackageManager.NameNotFoundException;
-    method @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
-    method @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
-    method @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, int);
+    method @Nullable public android.content.pm.ResolveInfo resolveActivity(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
+    method @Deprecated @Nullable public abstract android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, int);
+    method @Nullable public android.content.pm.ProviderInfo resolveContentProvider(@NonNull String, @NonNull android.content.pm.PackageManager.ComponentInfoFlags);
+    method @Deprecated @Nullable public abstract android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, int);
+    method @Nullable public android.content.pm.ResolveInfo resolveService(@NonNull android.content.Intent, @NonNull android.content.pm.PackageManager.ResolveInfoFlags);
     method public abstract void setApplicationCategoryHint(@NonNull String, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setApplicationEnabledSetting(@NonNull String, int, int);
     method @RequiresPermission(value="android.permission.WHITELIST_AUTO_REVOKE_PERMISSIONS", conditional=true) public boolean setAutoRevokeWhitelisted(@NonNull String, boolean);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public abstract void setComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
+    method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setComponentEnabledSettings(@NonNull java.util.List<android.content.pm.PackageManager.ComponentEnabledSetting>);
     method public abstract void setInstallerPackageName(@NonNull String, @Nullable String);
     method public void setMimeGroup(@NonNull String, @NonNull java.util.Set<java.lang.String>);
     method public abstract void updateInstantAppCookie(@Nullable byte[]);
@@ -12657,7 +13007,7 @@
     field public static final String FEATURE_CAMERA_LEVEL_FULL = "android.hardware.camera.level.full";
     field public static final String FEATURE_CANT_SAVE_STATE = "android.software.cant_save_state";
     field public static final String FEATURE_COMPANION_DEVICE_SETUP = "android.software.companion_device_setup";
-    field public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
+    field @Deprecated public static final String FEATURE_CONNECTION_SERVICE = "android.software.connectionservice";
     field public static final String FEATURE_CONSUMER_IR = "android.hardware.consumerir";
     field public static final String FEATURE_CONTROLS = "android.software.controls";
     field public static final String FEATURE_DEVICE_ADMIN = "android.software.device_admin";
@@ -12728,12 +13078,18 @@
     field public static final String FEATURE_SIP = "android.software.sip";
     field public static final String FEATURE_SIP_VOIP = "android.software.sip.voip";
     field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
+    field public static final String FEATURE_TELECOM = "android.software.telecom";
     field public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
+    field public static final String FEATURE_TELEPHONY_CALLING = "android.hardware.telephony.calling";
     field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+    field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
     field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
     field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
     field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
     field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
+    field public static final String FEATURE_TELEPHONY_MESSAGING = "android.hardware.telephony.messaging";
+    field public static final String FEATURE_TELEPHONY_RADIO_ACCESS = "android.hardware.telephony.radio";
+    field public static final String FEATURE_TELEPHONY_SUBSCRIPTION = "android.hardware.telephony.subscription";
     field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
     field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
     field public static final String FEATURE_TOUCHSCREEN_MULTITOUCH = "android.hardware.touchscreen.multitouch";
@@ -12815,6 +13171,26 @@
     field public static final int VERSION_CODE_HIGHEST = -1; // 0xffffffff
   }
 
+  public static final class PackageManager.ApplicationInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ApplicationInfoFlags of(long);
+  }
+
+  public static final class PackageManager.ComponentEnabledSetting implements android.os.Parcelable {
+    ctor public PackageManager.ComponentEnabledSetting(@NonNull android.content.ComponentName, int, int);
+    method public int describeContents();
+    method @Nullable public android.content.ComponentName getComponentName();
+    method public int getEnabledFlags();
+    method public int getEnabledState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.ComponentEnabledSetting> CREATOR;
+  }
+
+  public static final class PackageManager.ComponentInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ComponentInfoFlags of(long);
+  }
+
   public static class PackageManager.NameNotFoundException extends android.util.AndroidException {
     ctor public PackageManager.NameNotFoundException();
     ctor public PackageManager.NameNotFoundException(String);
@@ -12824,6 +13200,11 @@
     method public void onChecksumsReady(@NonNull java.util.List<android.content.pm.ApkChecksum>);
   }
 
+  public static final class PackageManager.PackageInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.PackageInfoFlags of(long);
+  }
+
   public static final class PackageManager.Property implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getBoolean();
@@ -12843,6 +13224,11 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.PackageManager.Property> CREATOR;
   }
 
+  public static final class PackageManager.ResolveInfoFlags {
+    method public long getValue();
+    method @NonNull public static android.content.pm.PackageManager.ResolveInfoFlags of(long);
+  }
+
   @Deprecated public class PackageStats implements android.os.Parcelable {
     ctor @Deprecated public PackageStats(String);
     ctor @Deprecated public PackageStats(android.os.Parcel);
@@ -13010,6 +13396,7 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.SharedLibraryInfo> CREATOR;
     field public static final int TYPE_BUILTIN = 0; // 0x0
     field public static final int TYPE_DYNAMIC = 1; // 0x1
+    field public static final int TYPE_SDK = 3; // 0x3
     field public static final int TYPE_STATIC = 2; // 0x2
     field public static final int VERSION_UNDEFINED = -1; // 0xffffffff
   }
@@ -13037,6 +13424,7 @@
     method public boolean isDynamic();
     method public boolean isEnabled();
     method public boolean isImmutable();
+    method public boolean isIncludedIn(int);
     method public boolean isPinned();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
@@ -13049,6 +13437,7 @@
     field public static final int DISABLED_REASON_UNKNOWN = 3; // 0x3
     field public static final int DISABLED_REASON_VERSION_LOWER = 100; // 0x64
     field public static final String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
+    field public static final int SURFACE_LAUNCHER = 1; // 0x1
   }
 
   public static class ShortcutInfo.Builder {
@@ -13057,6 +13446,7 @@
     method @NonNull public android.content.pm.ShortcutInfo.Builder setActivity(@NonNull android.content.ComponentName);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setDisabledMessage(@NonNull CharSequence);
+    method @NonNull public android.content.pm.ShortcutInfo.Builder setExcludedFromSurfaces(int);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setExtras(@NonNull android.os.PersistableBundle);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon);
     method @NonNull public android.content.pm.ShortcutInfo.Builder setIntent(@NonNull android.content.Intent);
@@ -13231,6 +13621,7 @@
     method public int describeContents();
     method public int diff(android.content.res.Configuration);
     method public boolean equals(android.content.res.Configuration);
+    method @NonNull public static android.content.res.Configuration generateDelta(@NonNull android.content.res.Configuration, @NonNull android.content.res.Configuration);
     method public int getLayoutDirection();
     method @NonNull public android.os.LocaleList getLocales();
     method public boolean isLayoutSizeAtLeast(int);
@@ -13384,7 +13775,7 @@
     method public float getFloat(@DimenRes int);
     method @NonNull public android.graphics.Typeface getFont(@FontRes int) throws android.content.res.Resources.NotFoundException;
     method public float getFraction(@FractionRes int, int, int);
-    method public int getIdentifier(String, String, String);
+    method @Discouraged(message="Use of this function is discouraged because resource reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resources by identifier (e.g. `R.foo.bar`) than by name (e.g. `getIdentifier(\"bar\", \"foo\", null)`).") public int getIdentifier(String, String, String);
     method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
@@ -13404,7 +13795,7 @@
     method public CharSequence getText(@StringRes int, CharSequence);
     method @NonNull public CharSequence[] getTextArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public void getValue(@AnyRes int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
-    method public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
+    method @Discouraged(message="Use of this function is discouraged because it makes internal calls to `getIdentifier()`, which uses resource reflection. Reflection makes it harder to perform build optimizations and compile-time verification of code. It is much more efficient to retrieve resource values by identifier (e.g. `getValue(R.foo.bar, outValue, true)`) than by name (e.g. `getValue(\"foo\", outvalue, true)`).") public void getValue(String, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
     method public void getValueForDensity(@AnyRes int, int, android.util.TypedValue, boolean) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getXml(@XmlRes int) throws android.content.res.Resources.NotFoundException;
     method public final android.content.res.Resources.Theme newTheme();
@@ -14072,11 +14463,21 @@
     field public static final int CONFLICT_ROLLBACK = 1; // 0x1
     field public static final int CREATE_IF_NECESSARY = 268435456; // 0x10000000
     field public static final int ENABLE_WRITE_AHEAD_LOGGING = 536870912; // 0x20000000
+    field public static final String JOURNAL_MODE_DELETE = "DELETE";
+    field public static final String JOURNAL_MODE_MEMORY = "MEMORY";
+    field public static final String JOURNAL_MODE_OFF = "OFF";
+    field public static final String JOURNAL_MODE_PERSIST = "PERSIST";
+    field public static final String JOURNAL_MODE_TRUNCATE = "TRUNCATE";
+    field public static final String JOURNAL_MODE_WAL = "WAL";
     field public static final int MAX_SQL_CACHE_SIZE = 100; // 0x64
     field public static final int NO_LOCALIZED_COLLATORS = 16; // 0x10
     field public static final int OPEN_READONLY = 1; // 0x1
     field public static final int OPEN_READWRITE = 0; // 0x0
     field public static final int SQLITE_MAX_LIKE_PATTERN_LENGTH = 50000; // 0xc350
+    field public static final String SYNC_MODE_EXTRA = "EXTRA";
+    field public static final String SYNC_MODE_FULL = "FULL";
+    field public static final String SYNC_MODE_NORMAL = "NORMAL";
+    field public static final String SYNC_MODE_OFF = "OFF";
   }
 
   public static interface SQLiteDatabase.CursorFactory {
@@ -14956,7 +15357,7 @@
     method public void drawTextRun(@NonNull android.graphics.text.MeasuredText, int, int, int, int, float, float, boolean, @NonNull android.graphics.Paint);
     method public void drawVertices(@NonNull android.graphics.Canvas.VertexMode, int, @NonNull float[], int, @Nullable float[], int, @Nullable int[], int, @Nullable short[], int, int, @NonNull android.graphics.Paint);
     method public void enableZ();
-    method public boolean getClipBounds(@Nullable android.graphics.Rect);
+    method public boolean getClipBounds(@NonNull android.graphics.Rect);
     method @NonNull public final android.graphics.Rect getClipBounds();
     method public int getDensity();
     method @Nullable public android.graphics.DrawFilter getDrawFilter();
@@ -15251,9 +15652,11 @@
     method public void clearContent();
     method @NonNull public android.graphics.HardwareRenderer.FrameRenderRequest createRenderRequest();
     method public void destroy();
+    method public static boolean isDrawingEnabled();
     method public boolean isOpaque();
     method public void notifyFramePending();
     method public void setContentRoot(@Nullable android.graphics.RenderNode);
+    method public static void setDrawingEnabled(boolean);
     method public void setLightSourceAlpha(@FloatRange(from=0.0f, to=1.0f) float, @FloatRange(from=0.0f, to=1.0f) float);
     method public void setLightSourceGeometry(float, float, float, float);
     method public void setName(@NonNull String);
@@ -16128,6 +16531,7 @@
     method @NonNull public static android.graphics.RenderEffect createColorFilterEffect(@NonNull android.graphics.ColorFilter);
     method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float);
     method @NonNull public static android.graphics.RenderEffect createOffsetEffect(float, float, @NonNull android.graphics.RenderEffect);
+    method @NonNull public static android.graphics.RenderEffect createRuntimeShaderEffect(@NonNull android.graphics.RuntimeShader, @NonNull String);
     method @NonNull public static android.graphics.RenderEffect createShaderEffect(@NonNull android.graphics.Shader);
   }
 
@@ -16203,6 +16607,26 @@
     method public boolean setUseCompositingLayer(boolean, @Nullable android.graphics.Paint);
   }
 
+  public class RuntimeShader extends android.graphics.Shader {
+    ctor public RuntimeShader(@NonNull String);
+    ctor public RuntimeShader(@NonNull String, boolean);
+    method public boolean isForceOpaque();
+    method public void setColorUniform(@NonNull String, @ColorInt int);
+    method public void setColorUniform(@NonNull String, @ColorLong long);
+    method public void setColorUniform(@NonNull String, @NonNull android.graphics.Color);
+    method public void setFloatUniform(@NonNull String, float);
+    method public void setFloatUniform(@NonNull String, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float);
+    method public void setFloatUniform(@NonNull String, float, float, float, float);
+    method public void setFloatUniform(@NonNull String, @NonNull float[]);
+    method public void setInputShader(@NonNull String, @NonNull android.graphics.Shader);
+    method public void setIntUniform(@NonNull String, int);
+    method public void setIntUniform(@NonNull String, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int);
+    method public void setIntUniform(@NonNull String, int, int, int, int);
+    method public void setIntUniform(@NonNull String, @NonNull int[]);
+  }
+
   public class Shader {
     ctor @Deprecated public Shader();
     method public boolean getLocalMatrix(@NonNull android.graphics.Matrix);
@@ -16226,6 +16650,7 @@
     ctor public SurfaceTexture(boolean);
     method public void attachToGLContext(int);
     method public void detachFromGLContext();
+    method public long getDataSpace();
     method public long getTimestamp();
     method public void getTransformMatrix(float[]);
     method public boolean isReleased();
@@ -16319,11 +16744,13 @@
 
   public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
     ctor public AdaptiveIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+    ctor public AdaptiveIconDrawable(@Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable, @Nullable android.graphics.drawable.Drawable);
     method public void draw(android.graphics.Canvas);
     method public android.graphics.drawable.Drawable getBackground();
     method public static float getExtraInsetFraction();
     method public android.graphics.drawable.Drawable getForeground();
     method public android.graphics.Path getIconMask();
+    method @Nullable public android.graphics.drawable.Drawable getMonochrome();
     method public int getOpacity();
     method public void invalidateDrawable(@NonNull android.graphics.drawable.Drawable);
     method public void scheduleDrawable(@NonNull android.graphics.drawable.Drawable, @NonNull Runnable, long);
@@ -17133,8 +17560,12 @@
     method @NonNull public android.graphics.text.MeasuredText.Builder appendReplacementRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, @FloatRange(from=0) @Px float);
     method @NonNull public android.graphics.text.MeasuredText.Builder appendStyleRun(@NonNull android.graphics.Paint, @IntRange(from=0) int, boolean);
     method @NonNull public android.graphics.text.MeasuredText build();
-    method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+    method @Deprecated @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(boolean);
+    method @NonNull public android.graphics.text.MeasuredText.Builder setComputeHyphenation(int);
     method @NonNull public android.graphics.text.MeasuredText.Builder setComputeLayout(boolean);
+    field public static final int HYPHENATION_MODE_FAST = 2; // 0x2
+    field public static final int HYPHENATION_MODE_NONE = 0; // 0x0
+    field public static final int HYPHENATION_MODE_NORMAL = 1; // 0x1
   }
 
   public final class PositionedGlyphs {
@@ -17437,6 +17868,52 @@
     method public int getMinFrequency();
   }
 
+  public final class DataSpace {
+    method public static long getRange(long);
+    method public static long getStandard(long);
+    method public static long getTransfer(long);
+    method public static long pack(long, long, long);
+    field public static final long DATASPACE_ADOBE_RGB = 151715840L; // 0x90b0000L
+    field public static final long DATASPACE_BT2020 = 147193856L; // 0x8c60000L
+    field public static final long DATASPACE_BT2020_PQ = 163971072L; // 0x9c60000L
+    field public static final long DATASPACE_BT601_525 = 281280512L; // 0x10c40000L
+    field public static final long DATASPACE_BT601_625 = 281149440L; // 0x10c20000L
+    field public static final long DATASPACE_BT709 = 281083904L; // 0x10c10000L
+    field public static final long DATASPACE_DCI_P3 = 155844608L; // 0x94a0000L
+    field public static final long DATASPACE_DISPLAY_P3 = 143261696L; // 0x88a0000L
+    field public static final long DATASPACE_JFIF = 146931712L; // 0x8c20000L
+    field public static final long DATASPACE_SCRGB = 411107328L; // 0x18810000L
+    field public static final long DATASPACE_SCRGB_LINEAR = 406913024L; // 0x18410000L
+    field public static final long DATASPACE_SRGB = 142671872L; // 0x8810000L
+    field public static final long DATASPACE_SRGB_LINEAR = 138477568L; // 0x8410000L
+    field public static final long DATASPACE_UNKNOWN = 0L; // 0x0L
+    field public static final long RANGE_EXTENDED = 402653184L; // 0x18000000L
+    field public static final long RANGE_FULL = 134217728L; // 0x8000000L
+    field public static final long RANGE_LIMITED = 268435456L; // 0x10000000L
+    field public static final long RANGE_UNSPECIFIED = 0L; // 0x0L
+    field public static final long STANDARD_ADOBE_RGB = 720896L; // 0xb0000L
+    field public static final long STANDARD_BT2020 = 393216L; // 0x60000L
+    field public static final long STANDARD_BT2020_CONSTANT_LUMINANCE = 458752L; // 0x70000L
+    field public static final long STANDARD_BT470M = 524288L; // 0x80000L
+    field public static final long STANDARD_BT601_525 = 262144L; // 0x40000L
+    field public static final long STANDARD_BT601_525_UNADJUSTED = 327680L; // 0x50000L
+    field public static final long STANDARD_BT601_625 = 131072L; // 0x20000L
+    field public static final long STANDARD_BT601_625_UNADJUSTED = 196608L; // 0x30000L
+    field public static final long STANDARD_BT709 = 65536L; // 0x10000L
+    field public static final long STANDARD_DCI_P3 = 655360L; // 0xa0000L
+    field public static final long STANDARD_FILM = 589824L; // 0x90000L
+    field public static final long STANDARD_UNSPECIFIED = 0L; // 0x0L
+    field public static final long TRANSFER_GAMMA2_2 = 16777216L; // 0x1000000L
+    field public static final long TRANSFER_GAMMA2_6 = 20971520L; // 0x1400000L
+    field public static final long TRANSFER_GAMMA2_8 = 25165824L; // 0x1800000L
+    field public static final long TRANSFER_HLG = 33554432L; // 0x2000000L
+    field public static final long TRANSFER_LINEAR = 4194304L; // 0x400000L
+    field public static final long TRANSFER_SMPTE_170M = 12582912L; // 0xc00000L
+    field public static final long TRANSFER_SRGB = 8388608L; // 0x800000L
+    field public static final long TRANSFER_ST2084 = 29360128L; // 0x1c00000L
+    field public static final long TRANSFER_UNSPECIFIED = 0L; // 0x0L
+  }
+
   public class GeomagneticField {
     ctor public GeomagneticField(float, float, float, long);
     method public float getDeclination();
@@ -17474,6 +17951,7 @@
     field public static final int RGB_565 = 4; // 0x4
     field public static final int RGB_888 = 3; // 0x3
     field public static final int S_UI8 = 53; // 0x35
+    field public static final long USAGE_COMPOSER_OVERLAY = 2048L; // 0x800L
     field public static final long USAGE_CPU_READ_OFTEN = 3L; // 0x3L
     field public static final long USAGE_CPU_READ_RARELY = 2L; // 0x2L
     field public static final long USAGE_CPU_WRITE_OFTEN = 48L; // 0x30L
@@ -17939,7 +18417,10 @@
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> DISTORTION_CORRECTION_AVAILABLE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> EDGE_AVAILABLE_EDGE_MODES;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> FLASH_INFO_AVAILABLE;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_DEFAULT_LEVEL;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> FLASH_INFO_STRENGTH_MAXIMUM_LEVEL;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES;
+    field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.hardware.camera2.params.DeviceStateSensorOrientationMap> INFO_DEVICE_STATE_SENSOR_ORIENTATION_MAP;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> INFO_SUPPORTED_HARDWARE_LEVEL;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.String> INFO_VERSION;
     field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Size[]> JPEG_AVAILABLE_THUMBNAIL_SIZES;
@@ -18074,8 +18555,9 @@
     method @NonNull public java.util.List<android.util.Size> getExtensionSupportedSizes(int, int);
     method @NonNull public java.util.List<java.lang.Integer> getSupportedExtensions();
     field public static final int EXTENSION_AUTOMATIC = 0; // 0x0
-    field public static final int EXTENSION_BEAUTY = 1; // 0x1
+    field @Deprecated public static final int EXTENSION_BEAUTY = 1; // 0x1
     field public static final int EXTENSION_BOKEH = 2; // 0x2
+    field public static final int EXTENSION_FACE_RETOUCH = 1; // 0x1
     field public static final int EXTENSION_HDR = 3; // 0x3
     field public static final int EXTENSION_NIGHT = 4; // 0x4
   }
@@ -18109,6 +18591,7 @@
     method @NonNull public android.hardware.camera2.CameraExtensionCharacteristics getCameraExtensionCharacteristics(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @NonNull public String[] getCameraIdList() throws android.hardware.camera2.CameraAccessException;
     method @NonNull public java.util.Set<java.util.Set<java.lang.String>> getConcurrentCameraIds() throws android.hardware.camera2.CameraAccessException;
+    method public int getTorchStrengthLevel(@NonNull String) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public boolean isConcurrentSessionConfigurationSupported(@NonNull java.util.Map<java.lang.String,android.hardware.camera2.params.SessionConfiguration>) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull android.hardware.camera2.CameraDevice.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
     method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException;
@@ -18117,6 +18600,7 @@
     method public void registerTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback, @Nullable android.os.Handler);
     method public void registerTorchCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraManager.TorchCallback);
     method public void setTorchMode(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException;
+    method public void turnOnTorchWithStrengthLevel(@NonNull String, int) throws android.hardware.camera2.CameraAccessException;
     method public void unregisterAvailabilityCallback(@NonNull android.hardware.camera2.CameraManager.AvailabilityCallback);
     method public void unregisterTorchCallback(@NonNull android.hardware.camera2.CameraManager.TorchCallback);
   }
@@ -18134,6 +18618,7 @@
     ctor public CameraManager.TorchCallback();
     method public void onTorchModeChanged(@NonNull String, boolean);
     method public void onTorchModeUnavailable(@NonNull String);
+    method public void onTorchStrengthLevelChanged(@NonNull String, int);
   }
 
   public abstract class CameraMetadata<TKey> {
@@ -18240,6 +18725,7 @@
     field public static final int CONTROL_SCENE_MODE_THEATRE = 7; // 0x7
     field public static final int CONTROL_VIDEO_STABILIZATION_MODE_OFF = 0; // 0x0
     field public static final int CONTROL_VIDEO_STABILIZATION_MODE_ON = 1; // 0x1
+    field public static final int CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION = 2; // 0x2
     field public static final int DISTORTION_CORRECTION_MODE_FAST = 1; // 0x1
     field public static final int DISTORTION_CORRECTION_MODE_HIGH_QUALITY = 2; // 0x2
     field public static final int DISTORTION_CORRECTION_MODE_OFF = 0; // 0x0
@@ -18634,6 +19120,12 @@
     method public android.util.Rational getElement(int, int);
   }
 
+  public final class DeviceStateSensorOrientationMap {
+    method public int getSensorOrientation(long);
+    field public static final long FOLDED = 4L; // 0x4L
+    field public static final long NORMAL = 0L; // 0x0L
+  }
+
   public final class ExtensionSessionConfiguration {
     ctor public ExtensionSessionConfiguration(int, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraExtensionSession.StateCallback);
     method @NonNull public java.util.concurrent.Executor getExecutor();
@@ -19572,14 +20064,22 @@
   }
 
   public final class Geocoder {
-    ctor public Geocoder(android.content.Context, java.util.Locale);
-    ctor public Geocoder(android.content.Context);
-    method public java.util.List<android.location.Address> getFromLocation(double, double, int) throws java.io.IOException;
-    method public java.util.List<android.location.Address> getFromLocationName(String, int) throws java.io.IOException;
-    method public java.util.List<android.location.Address> getFromLocationName(String, int, double, double, double, double) throws java.io.IOException;
+    ctor public Geocoder(@NonNull android.content.Context);
+    ctor public Geocoder(@NonNull android.content.Context, @NonNull java.util.Locale);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int) throws java.io.IOException;
+    method public void getFromLocation(@FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int) throws java.io.IOException;
+    method public void getFromLocationName(@NonNull String, @IntRange int, @NonNull android.location.Geocoder.GeocodeListener);
+    method @Deprecated @Nullable public java.util.List<android.location.Address> getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double) throws java.io.IOException;
+    method public void getFromLocationName(@NonNull String, @IntRange int, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @FloatRange(from=-90.0, to=90.0) double, @FloatRange(from=-180.0, to=180.0) double, @NonNull android.location.Geocoder.GeocodeListener);
     method public static boolean isPresent();
   }
 
+  public static interface Geocoder.GeocodeListener {
+    method public default void onError(@Nullable String);
+    method public void onGeocode(@NonNull java.util.List<android.location.Address>);
+  }
+
   public final class GnssAntennaInfo implements android.os.Parcelable {
     method public int describeContents();
     method @FloatRange(from=0.0f) public double getCarrierFrequencyMHz();
@@ -19752,6 +20252,7 @@
 
   public final class GnssMeasurementRequest implements android.os.Parcelable {
     method public int describeContents();
+    method @IntRange(from=0) public int getIntervalMillis();
     method public boolean isFullTracking();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssMeasurementRequest> CREATOR;
@@ -19762,6 +20263,7 @@
     ctor public GnssMeasurementRequest.Builder(@NonNull android.location.GnssMeasurementRequest);
     method @NonNull public android.location.GnssMeasurementRequest build();
     method @NonNull public android.location.GnssMeasurementRequest.Builder setFullTracking(boolean);
+    method @NonNull public android.location.GnssMeasurementRequest.Builder setIntervalMillis(@IntRange(from=0) int);
   }
 
   public final class GnssMeasurementsEvent implements android.os.Parcelable {
@@ -19893,29 +20395,32 @@
   }
 
   public class Location implements android.os.Parcelable {
-    ctor public Location(String);
-    ctor public Location(android.location.Location);
-    method public float bearingTo(android.location.Location);
-    method public static String convert(double, int);
-    method public static double convert(String);
+    ctor public Location(@Nullable String);
+    ctor public Location(@NonNull android.location.Location);
+    method public float bearingTo(@NonNull android.location.Location);
+    method @NonNull public static String convert(@FloatRange double, int);
+    method @FloatRange public static double convert(@NonNull String);
     method public int describeContents();
-    method public static void distanceBetween(double, double, double, double, float[]);
-    method public float distanceTo(android.location.Location);
-    method public void dump(android.util.Printer, String);
-    method public float getAccuracy();
-    method public double getAltitude();
-    method public float getBearing();
-    method public float getBearingAccuracyDegrees();
-    method public long getElapsedRealtimeNanos();
-    method public double getElapsedRealtimeUncertaintyNanos();
-    method public android.os.Bundle getExtras();
-    method public double getLatitude();
-    method public double getLongitude();
-    method public String getProvider();
-    method public float getSpeed();
-    method public float getSpeedAccuracyMetersPerSecond();
-    method public long getTime();
-    method public float getVerticalAccuracyMeters();
+    method public static void distanceBetween(@FloatRange double, @FloatRange double, @FloatRange double, @FloatRange double, float[]);
+    method @FloatRange public float distanceTo(@NonNull android.location.Location);
+    method public void dump(@NonNull android.util.Printer, @Nullable String);
+    method @FloatRange public float getAccuracy();
+    method @FloatRange public double getAltitude();
+    method @FloatRange(from=0.0f, to=360.0f, toInclusive=false) public float getBearing();
+    method @FloatRange public float getBearingAccuracyDegrees();
+    method @IntRange public long getElapsedRealtimeAgeMillis();
+    method @IntRange public long getElapsedRealtimeAgeMillis(@IntRange long);
+    method @IntRange public long getElapsedRealtimeMillis();
+    method @IntRange public long getElapsedRealtimeNanos();
+    method @FloatRange public double getElapsedRealtimeUncertaintyNanos();
+    method @Nullable public android.os.Bundle getExtras();
+    method @FloatRange public double getLatitude();
+    method @FloatRange public double getLongitude();
+    method @Nullable public String getProvider();
+    method @FloatRange public float getSpeed();
+    method @FloatRange public float getSpeedAccuracyMetersPerSecond();
+    method @IntRange public long getTime();
+    method @FloatRange public float getVerticalAccuracyMeters();
     method public boolean hasAccuracy();
     method public boolean hasAltitude();
     method public boolean hasBearing();
@@ -19924,30 +20429,35 @@
     method public boolean hasSpeed();
     method public boolean hasSpeedAccuracy();
     method public boolean hasVerticalAccuracy();
+    method public boolean isComplete();
     method @Deprecated public boolean isFromMockProvider();
     method public boolean isMock();
-    method @Deprecated public void removeAccuracy();
-    method @Deprecated public void removeAltitude();
-    method @Deprecated public void removeBearing();
-    method @Deprecated public void removeSpeed();
+    method public void removeAccuracy();
+    method public void removeAltitude();
+    method public void removeBearing();
+    method public void removeBearingAccuracy();
+    method public void removeElapsedRealtimeUncertaintyNanos();
+    method public void removeSpeed();
+    method public void removeSpeedAccuracy();
+    method public void removeVerticalAccuracy();
     method public void reset();
-    method public void set(android.location.Location);
-    method public void setAccuracy(float);
-    method public void setAltitude(double);
-    method public void setBearing(float);
-    method public void setBearingAccuracyDegrees(float);
-    method public void setElapsedRealtimeNanos(long);
-    method public void setElapsedRealtimeUncertaintyNanos(double);
+    method public void set(@NonNull android.location.Location);
+    method public void setAccuracy(@FloatRange float);
+    method public void setAltitude(@FloatRange double);
+    method public void setBearing(@FloatRange(fromInclusive=false, toInclusive=false) float);
+    method public void setBearingAccuracyDegrees(@FloatRange float);
+    method public void setElapsedRealtimeNanos(@IntRange long);
+    method public void setElapsedRealtimeUncertaintyNanos(@FloatRange double);
     method public void setExtras(@Nullable android.os.Bundle);
-    method public void setLatitude(double);
-    method public void setLongitude(double);
+    method public void setLatitude(@FloatRange double);
+    method public void setLongitude(@FloatRange double);
     method public void setMock(boolean);
-    method public void setProvider(String);
-    method public void setSpeed(float);
-    method public void setSpeedAccuracyMetersPerSecond(float);
-    method public void setTime(long);
-    method public void setVerticalAccuracyMeters(float);
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void setProvider(@Nullable String);
+    method public void setSpeed(@FloatRange float);
+    method public void setSpeedAccuracyMetersPerSecond(@FloatRange float);
+    method public void setTime(@IntRange long);
+    method public void setVerticalAccuracyMeters(@FloatRange float);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.location.Location> CREATOR;
     field public static final int FORMAT_DEGREES = 0; // 0x0
     field public static final int FORMAT_MINUTES = 1; // 0x1
@@ -20175,8 +20685,10 @@
     method public int getAllowedCapturePolicy();
     method public int getContentType();
     method public int getFlags();
+    method public int getSpatializationBehavior();
     method public int getUsage();
     method public int getVolumeControlStream();
+    method public boolean isContentSpatialized();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1
     field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3
@@ -20190,6 +20702,8 @@
     field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1
     field public static final int FLAG_HW_AV_SYNC = 16; // 0x10
     field @Deprecated public static final int FLAG_LOW_LATENCY = 256; // 0x100
+    field public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; // 0x0
+    field public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; // 0x1
     field public static final int USAGE_ALARM = 4; // 0x4
     field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb
     field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc
@@ -20198,9 +20712,9 @@
     field public static final int USAGE_GAME = 14; // 0xe
     field public static final int USAGE_MEDIA = 1; // 0x1
     field public static final int USAGE_NOTIFICATION = 5; // 0x5
-    field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
-    field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
-    field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
+    field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9
+    field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8
+    field @Deprecated public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7
     field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa
     field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6
     field public static final int USAGE_UNKNOWN = 0; // 0x0
@@ -20216,7 +20730,9 @@
     method public android.media.AudioAttributes.Builder setContentType(int);
     method public android.media.AudioAttributes.Builder setFlags(int);
     method @NonNull public android.media.AudioAttributes.Builder setHapticChannelsMuted(boolean);
+    method @NonNull public android.media.AudioAttributes.Builder setIsContentSpatialized(boolean);
     method public android.media.AudioAttributes.Builder setLegacyStreamType(int);
+    method @NonNull public android.media.AudioAttributes.Builder setSpatializationBehavior(int);
     method public android.media.AudioAttributes.Builder setUsage(int);
   }
 
@@ -20333,24 +20849,45 @@
     field public static final int CHANNEL_IN_Y_AXIS = 4096; // 0x1000
     field public static final int CHANNEL_IN_Z_AXIS = 8192; // 0x2000
     field public static final int CHANNEL_OUT_5POINT1 = 252; // 0xfc
+    field public static final int CHANNEL_OUT_5POINT1POINT2 = 3145980; // 0x3000fc
+    field public static final int CHANNEL_OUT_5POINT1POINT4 = 737532; // 0xb40fc
     field @Deprecated public static final int CHANNEL_OUT_7POINT1 = 1020; // 0x3fc
+    field public static final int CHANNEL_OUT_7POINT1POINT2 = 3152124; // 0x3018fc
+    field public static final int CHANNEL_OUT_7POINT1POINT4 = 743676; // 0xb58fc
     field public static final int CHANNEL_OUT_7POINT1_SURROUND = 6396; // 0x18fc
+    field public static final int CHANNEL_OUT_9POINT1POINT4 = 202070268; // 0xc0b58fc
+    field public static final int CHANNEL_OUT_9POINT1POINT6 = 205215996; // 0xc3b58fc
     field public static final int CHANNEL_OUT_BACK_CENTER = 1024; // 0x400
     field public static final int CHANNEL_OUT_BACK_LEFT = 64; // 0x40
     field public static final int CHANNEL_OUT_BACK_RIGHT = 128; // 0x80
+    field public static final int CHANNEL_OUT_BOTTOM_FRONT_CENTER = 8388608; // 0x800000
+    field public static final int CHANNEL_OUT_BOTTOM_FRONT_LEFT = 4194304; // 0x400000
+    field public static final int CHANNEL_OUT_BOTTOM_FRONT_RIGHT = 16777216; // 0x1000000
     field public static final int CHANNEL_OUT_DEFAULT = 1; // 0x1
     field public static final int CHANNEL_OUT_FRONT_CENTER = 16; // 0x10
     field public static final int CHANNEL_OUT_FRONT_LEFT = 4; // 0x4
     field public static final int CHANNEL_OUT_FRONT_LEFT_OF_CENTER = 256; // 0x100
     field public static final int CHANNEL_OUT_FRONT_RIGHT = 8; // 0x8
     field public static final int CHANNEL_OUT_FRONT_RIGHT_OF_CENTER = 512; // 0x200
+    field public static final int CHANNEL_OUT_FRONT_WIDE_LEFT = 67108864; // 0x4000000
+    field public static final int CHANNEL_OUT_FRONT_WIDE_RIGHT = 134217728; // 0x8000000
     field public static final int CHANNEL_OUT_LOW_FREQUENCY = 32; // 0x20
+    field public static final int CHANNEL_OUT_LOW_FREQUENCY_2 = 33554432; // 0x2000000
     field public static final int CHANNEL_OUT_MONO = 4; // 0x4
     field public static final int CHANNEL_OUT_QUAD = 204; // 0xcc
     field public static final int CHANNEL_OUT_SIDE_LEFT = 2048; // 0x800
     field public static final int CHANNEL_OUT_SIDE_RIGHT = 4096; // 0x1000
     field public static final int CHANNEL_OUT_STEREO = 12; // 0xc
     field public static final int CHANNEL_OUT_SURROUND = 1052; // 0x41c
+    field public static final int CHANNEL_OUT_TOP_BACK_CENTER = 262144; // 0x40000
+    field public static final int CHANNEL_OUT_TOP_BACK_LEFT = 131072; // 0x20000
+    field public static final int CHANNEL_OUT_TOP_BACK_RIGHT = 524288; // 0x80000
+    field public static final int CHANNEL_OUT_TOP_CENTER = 8192; // 0x2000
+    field public static final int CHANNEL_OUT_TOP_FRONT_CENTER = 32768; // 0x8000
+    field public static final int CHANNEL_OUT_TOP_FRONT_LEFT = 16384; // 0x4000
+    field public static final int CHANNEL_OUT_TOP_FRONT_RIGHT = 65536; // 0x10000
+    field public static final int CHANNEL_OUT_TOP_SIDE_LEFT = 1048576; // 0x100000
+    field public static final int CHANNEL_OUT_TOP_SIDE_RIGHT = 2097152; // 0x200000
     field @NonNull public static final android.os.Parcelable.Creator<android.media.AudioFormat> CREATOR;
     field public static final int ENCODING_AAC_ELD = 15; // 0xf
     field public static final int ENCODING_AAC_HE_V1 = 11; // 0xb
@@ -20412,14 +20949,16 @@
     method @NonNull public java.util.List<android.media.AudioDeviceInfo> getAvailableCommunicationDevices();
     method @Nullable public android.media.AudioDeviceInfo getCommunicationDevice();
     method public android.media.AudioDeviceInfo[] getDevices(int);
+    method public static int getDirectPlaybackSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
     method public int getEncodedSurroundMode();
     method public java.util.List<android.media.MicrophoneInfo> getMicrophones() throws java.io.IOException;
     method public int getMode();
     method public String getParameters(String);
-    method public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+    method @Deprecated public static int getPlaybackOffloadSupport(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
     method public String getProperty(String);
     method public int getRingerMode();
     method @Deprecated public int getRouting(int);
+    method @NonNull public android.media.Spatializer getSpatializer();
     method public int getStreamMaxVolume(int);
     method public int getStreamMinVolume(int);
     method public int getStreamVolume(int);
@@ -20433,6 +20972,7 @@
     method public boolean isMicrophoneMute();
     method public boolean isMusicActive();
     method public static boolean isOffloadedPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+    method public boolean isRampingRingerEnabled();
     method public boolean isSpeakerphoneOn();
     method public boolean isStreamMute(int);
     method public boolean isSurroundFormatEnabled(int);
@@ -20505,6 +21045,10 @@
     field public static final int AUDIOFOCUS_REQUEST_FAILED = 0; // 0x0
     field public static final int AUDIOFOCUS_REQUEST_GRANTED = 1; // 0x1
     field public static final int AUDIO_SESSION_ID_GENERATE = 0; // 0x0
+    field public static final int DIRECT_PLAYBACK_BITSTREAM_SUPPORTED = 4; // 0x4
+    field public static final int DIRECT_PLAYBACK_NOT_SUPPORTED = 0; // 0x0
+    field public static final int DIRECT_PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED = 3; // 0x3
+    field public static final int DIRECT_PLAYBACK_OFFLOAD_SUPPORTED = 1; // 0x1
     field public static final int ENCODED_SURROUND_OUTPUT_ALWAYS = 2; // 0x2
     field public static final int ENCODED_SURROUND_OUTPUT_AUTO = 0; // 0x0
     field public static final int ENCODED_SURROUND_OUTPUT_MANUAL = 3; // 0x3
@@ -20539,7 +21083,9 @@
     field public static final int GET_DEVICES_ALL = 3; // 0x3
     field public static final int GET_DEVICES_INPUTS = 1; // 0x1
     field public static final int GET_DEVICES_OUTPUTS = 2; // 0x2
+    field public static final int MODE_CALL_REDIRECT = 5; // 0x5
     field public static final int MODE_CALL_SCREENING = 4; // 0x4
+    field public static final int MODE_COMMUNICATION_REDIRECT = 6; // 0x6
     field public static final int MODE_CURRENT = -1; // 0xffffffff
     field public static final int MODE_INVALID = -2; // 0xfffffffe
     field public static final int MODE_IN_CALL = 2; // 0x2
@@ -20893,7 +21439,7 @@
     method public int getStreamType();
     method public boolean getTimestamp(android.media.AudioTimestamp);
     method public int getUnderrunCount();
-    method public static boolean isDirectPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
+    method @Deprecated public static boolean isDirectPlaybackSupported(@NonNull android.media.AudioFormat, @NonNull android.media.AudioAttributes);
     method public boolean isOffloadedPlayback();
     method public void pause() throws java.lang.IllegalStateException;
     method public void play() throws java.lang.IllegalStateException;
@@ -21317,6 +21863,7 @@
   public abstract class Image implements java.lang.AutoCloseable {
     method public abstract void close();
     method public android.graphics.Rect getCropRect();
+    method public long getDataSpace();
     method public abstract int getFormat();
     method @Nullable public android.hardware.HardwareBuffer getHardwareBuffer();
     method public abstract int getHeight();
@@ -21324,6 +21871,7 @@
     method public abstract long getTimestamp();
     method public abstract int getWidth();
     method public void setCropRect(android.graphics.Rect);
+    method public void setDataSpace(long);
     method public void setTimestamp(long);
   }
 
@@ -21399,6 +21947,7 @@
   public class MediaActionSound {
     ctor public MediaActionSound();
     method public void load(int);
+    method public static boolean mustPlayShutterSound();
     method public void play(int);
     method public void release();
     field public static final int FOCUS_COMPLETE = 1; // 0x1
@@ -22420,7 +22969,7 @@
     method public void setDataSource(@NonNull java.io.FileDescriptor) throws java.io.IOException;
     method public void setDataSource(@NonNull java.io.FileDescriptor, long, long) throws java.io.IOException;
     method public void setLogSessionId(@NonNull android.media.metrics.LogSessionId);
-    method public void setMediaCas(@NonNull android.media.MediaCas);
+    method @Deprecated public void setMediaCas(@NonNull android.media.MediaCas);
     method public void unselectTrack(int);
     field public static final int SAMPLE_FLAG_ENCRYPTED = 2; // 0x2
     field public static final int SAMPLE_FLAG_PARTIAL_FRAME = 4; // 0x4
@@ -22537,6 +23086,7 @@
     field public static final String KEY_MAX_FPS_TO_ENCODER = "max-fps-to-encoder";
     field public static final String KEY_MAX_HEIGHT = "max-height";
     field public static final String KEY_MAX_INPUT_SIZE = "max-input-size";
+    field public static final String KEY_MAX_OUTPUT_CHANNEL_COUNT = "max-output-channel-count";
     field public static final String KEY_MAX_PTS_GAP_TO_ENCODER = "max-pts-gap-to-encoder";
     field public static final String KEY_MAX_WIDTH = "max-width";
     field public static final String KEY_MIME = "mime";
@@ -22573,16 +23123,32 @@
     field public static final String KEY_VIDEO_QP_P_MIN = "video-qp-p-min";
     field public static final String KEY_WIDTH = "width";
     field public static final String MIMETYPE_AUDIO_AAC = "audio/mp4a-latm";
+    field public static final String MIMETYPE_AUDIO_AAC_ELD = "audio/mp4a.40.39";
+    field public static final String MIMETYPE_AUDIO_AAC_HE_V1 = "audio/mp4a.40.05";
+    field public static final String MIMETYPE_AUDIO_AAC_HE_V2 = "audio/mp4a.40.29";
+    field public static final String MIMETYPE_AUDIO_AAC_LC = "audio/mp4a.40.02";
+    field public static final String MIMETYPE_AUDIO_AAC_XHE = "audio/mp4a.40.42";
     field public static final String MIMETYPE_AUDIO_AC3 = "audio/ac3";
     field public static final String MIMETYPE_AUDIO_AC4 = "audio/ac4";
     field public static final String MIMETYPE_AUDIO_AMR_NB = "audio/3gpp";
     field public static final String MIMETYPE_AUDIO_AMR_WB = "audio/amr-wb";
+    field public static final String MIMETYPE_AUDIO_DOLBY_MAT = "audio/vnd.dolby.mat";
+    field public static final String MIMETYPE_AUDIO_DOLBY_TRUEHD = "audio/vnd.dolby.mlp";
+    field public static final String MIMETYPE_AUDIO_DRA = "audio/vnd.dra";
+    field public static final String MIMETYPE_AUDIO_DTS = "audio/vnd.dts";
+    field public static final String MIMETYPE_AUDIO_DTS_HD = "audio/vnd.dts.hd";
+    field public static final String MIMETYPE_AUDIO_DTS_UHD = "audio/vnd.dts.uhd";
     field public static final String MIMETYPE_AUDIO_EAC3 = "audio/eac3";
     field public static final String MIMETYPE_AUDIO_EAC3_JOC = "audio/eac3-joc";
     field public static final String MIMETYPE_AUDIO_FLAC = "audio/flac";
     field public static final String MIMETYPE_AUDIO_G711_ALAW = "audio/g711-alaw";
     field public static final String MIMETYPE_AUDIO_G711_MLAW = "audio/g711-mlaw";
+    field public static final String MIMETYPE_AUDIO_IEC61937 = "audio/x-iec61937";
     field public static final String MIMETYPE_AUDIO_MPEG = "audio/mpeg";
+    field public static final String MIMETYPE_AUDIO_MPEGH_BL_L3 = "audio/mhm1.03";
+    field public static final String MIMETYPE_AUDIO_MPEGH_BL_L4 = "audio/mhm1.04";
+    field public static final String MIMETYPE_AUDIO_MPEGH_LC_L3 = "audio/mhm1.0d";
+    field public static final String MIMETYPE_AUDIO_MPEGH_LC_L4 = "audio/mhm1.0e";
     field public static final String MIMETYPE_AUDIO_MPEGH_MHA1 = "audio/mha1";
     field public static final String MIMETYPE_AUDIO_MPEGH_MHM1 = "audio/mhm1";
     field public static final String MIMETYPE_AUDIO_MSGSM = "audio/gsm";
@@ -22693,7 +23259,7 @@
 
   public class MediaMetadataRetriever implements java.lang.AutoCloseable {
     ctor public MediaMetadataRetriever();
-    method public void close();
+    method public void close() throws java.io.IOException;
     method @Nullable public String extractMetadata(int);
     method @Nullable public byte[] getEmbeddedPicture();
     method @Nullable public android.graphics.Bitmap getFrameAtIndex(int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
@@ -22710,7 +23276,7 @@
     method @Nullable public android.graphics.Bitmap getPrimaryImage();
     method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int);
     method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
-    method public void release();
+    method public void release() throws java.io.IOException;
     method public void setDataSource(String) throws java.lang.IllegalArgumentException;
     method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
     method public void setDataSource(java.io.FileDescriptor, long, long) throws java.lang.IllegalArgumentException;
@@ -23816,6 +24382,23 @@
     method public void onLoadComplete(android.media.SoundPool, int, int);
   }
 
+  public class Spatializer {
+    method public void addOnSpatializerStateChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+    method public boolean canBeSpatialized(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioFormat);
+    method public int getImmersiveAudioLevel();
+    method public boolean isAvailable();
+    method public boolean isEnabled();
+    method public void removeOnSpatializerStateChangedListener(@NonNull android.media.Spatializer.OnSpatializerStateChangedListener);
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_MULTICHANNEL = 1; // 0x1
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_NONE = 0; // 0x0
+    field public static final int SPATIALIZER_IMMERSIVE_LEVEL_OTHER = -1; // 0xffffffff
+  }
+
+  public static interface Spatializer.OnSpatializerStateChangedListener {
+    method public void onSpatializerAvailableChanged(@NonNull android.media.Spatializer, boolean);
+    method public void onSpatializerEnabledChanged(@NonNull android.media.Spatializer, boolean);
+  }
+
   public final class SubtitleData {
     ctor public SubtitleData(int, long, long, @NonNull byte[]);
     method @NonNull public byte[] getData();
@@ -25229,13 +25812,17 @@
   public final class MediaSessionManager {
     method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName);
     method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @Nullable android.os.Handler);
+    method public void addOnMediaKeyEventSessionChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
     method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
     method public void addOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener, @NonNull android.os.Handler);
     method @NonNull public java.util.List<android.media.session.MediaController> getActiveSessions(@Nullable android.content.ComponentName);
+    method @Nullable public android.media.session.MediaSession.Token getMediaKeyEventSession();
+    method @NonNull public String getMediaKeyEventSessionPackageName();
     method @NonNull public java.util.List<android.media.Session2Token> getSession2Tokens();
     method public boolean isTrustedForMediaControl(@NonNull android.media.session.MediaSessionManager.RemoteUserInfo);
     method @Deprecated public void notifySession2Created(@NonNull android.media.Session2Token);
     method public void removeOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener);
+    method public void removeOnMediaKeyEventSessionChangedListener(@NonNull android.media.session.MediaSessionManager.OnMediaKeyEventSessionChangedListener);
     method public void removeOnSession2TokensChangedListener(@NonNull android.media.session.MediaSessionManager.OnSession2TokensChangedListener);
   }
 
@@ -25243,6 +25830,10 @@
     method public void onActiveSessionsChanged(@Nullable java.util.List<android.media.session.MediaController>);
   }
 
+  public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
+    method public void onMediaKeyEventSessionChanged(@NonNull String, @Nullable android.media.session.MediaSession.Token);
+  }
+
   public static interface MediaSessionManager.OnSession2TokensChangedListener {
     method public void onSession2TokensChanged(@NonNull java.util.List<android.media.Session2Token>);
   }
@@ -25496,6 +26087,7 @@
     field public static final String COLUMN_CONTENT_ID = "content_id";
     field public static final String COLUMN_CONTENT_RATING = "content_rating";
     field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+    field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
     field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
     field public static final String COLUMN_EPISODE_TITLE = "episode_title";
     field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25526,6 +26118,7 @@
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
     field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
+    field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -25579,11 +26172,14 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
     field public static final String COLUMN_REVIEW_RATING = "review_rating";
     field public static final String COLUMN_REVIEW_RATING_STYLE = "review_rating_style";
+    field public static final String COLUMN_SCRAMBLED = "scrambled";
     field public static final String COLUMN_SEARCHABLE = "searchable";
     field public static final String COLUMN_SEASON_DISPLAY_NUMBER = "season_display_number";
     field @Deprecated public static final String COLUMN_SEASON_NUMBER = "season_number";
@@ -25643,7 +26239,9 @@
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG3 = "internal_provider_flag3";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG4 = "internal_provider_flag4";
+    field public static final String COLUMN_INTERNAL_PROVIDER_ID = "internal_provider_id";
     field public static final String COLUMN_LONG_DESCRIPTION = "long_description";
+    field public static final String COLUMN_MULTI_SERIES_ID = "multi_series_id";
     field public static final String COLUMN_POSTER_ART_URI = "poster_art_uri";
     field public static final String COLUMN_RECORDING_DATA_BYTES = "recording_data_bytes";
     field public static final String COLUMN_RECORDING_DATA_URI = "recording_data_uri";
@@ -25688,6 +26286,7 @@
     field public static final String COLUMN_CONTENT_ID = "content_id";
     field public static final String COLUMN_CONTENT_RATING = "content_rating";
     field public static final String COLUMN_DURATION_MILLIS = "duration_millis";
+    field public static final String COLUMN_END_TIME_UTC_MILLIS = "end_time_utc_millis";
     field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
     field public static final String COLUMN_EPISODE_TITLE = "episode_title";
     field public static final String COLUMN_INTENT_URI = "intent_uri";
@@ -25719,6 +26318,7 @@
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
     field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
+    field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -26060,6 +26660,20 @@
 
 }
 
+package android.media.tv.interactive {
+
+  public final class TvIAppManager {
+  }
+
+  public abstract class TvIAppService extends android.app.Service {
+    ctor public TvIAppService();
+    method public final android.os.IBinder onBind(android.content.Intent);
+    field public static final String SERVICE_INTERFACE = "android.media.tv.interactive.TvIAppService";
+    field public static final String SERVICE_META_DATA = "android.media.tv.interactive.app";
+  }
+
+}
+
 package android.mtp {
 
   public final class MtpConstants {
@@ -26826,65 +27440,6 @@
 
 }
 
-package android.net.nsd {
-
-  public final class NsdManager {
-    method public void discoverServices(String, int, android.net.nsd.NsdManager.DiscoveryListener);
-    method public void registerService(android.net.nsd.NsdServiceInfo, int, android.net.nsd.NsdManager.RegistrationListener);
-    method public void resolveService(android.net.nsd.NsdServiceInfo, android.net.nsd.NsdManager.ResolveListener);
-    method public void stopServiceDiscovery(android.net.nsd.NsdManager.DiscoveryListener);
-    method public void unregisterService(android.net.nsd.NsdManager.RegistrationListener);
-    field public static final String ACTION_NSD_STATE_CHANGED = "android.net.nsd.STATE_CHANGED";
-    field public static final String EXTRA_NSD_STATE = "nsd_state";
-    field public static final int FAILURE_ALREADY_ACTIVE = 3; // 0x3
-    field public static final int FAILURE_INTERNAL_ERROR = 0; // 0x0
-    field public static final int FAILURE_MAX_LIMIT = 4; // 0x4
-    field public static final int NSD_STATE_DISABLED = 1; // 0x1
-    field public static final int NSD_STATE_ENABLED = 2; // 0x2
-    field public static final int PROTOCOL_DNS_SD = 1; // 0x1
-  }
-
-  public static interface NsdManager.DiscoveryListener {
-    method public void onDiscoveryStarted(String);
-    method public void onDiscoveryStopped(String);
-    method public void onServiceFound(android.net.nsd.NsdServiceInfo);
-    method public void onServiceLost(android.net.nsd.NsdServiceInfo);
-    method public void onStartDiscoveryFailed(String, int);
-    method public void onStopDiscoveryFailed(String, int);
-  }
-
-  public static interface NsdManager.RegistrationListener {
-    method public void onRegistrationFailed(android.net.nsd.NsdServiceInfo, int);
-    method public void onServiceRegistered(android.net.nsd.NsdServiceInfo);
-    method public void onServiceUnregistered(android.net.nsd.NsdServiceInfo);
-    method public void onUnregistrationFailed(android.net.nsd.NsdServiceInfo, int);
-  }
-
-  public static interface NsdManager.ResolveListener {
-    method public void onResolveFailed(android.net.nsd.NsdServiceInfo, int);
-    method public void onServiceResolved(android.net.nsd.NsdServiceInfo);
-  }
-
-  public final class NsdServiceInfo implements android.os.Parcelable {
-    ctor public NsdServiceInfo();
-    method public int describeContents();
-    method public java.util.Map<java.lang.String,byte[]> getAttributes();
-    method public java.net.InetAddress getHost();
-    method public int getPort();
-    method public String getServiceName();
-    method public String getServiceType();
-    method public void removeAttribute(String);
-    method public void setAttribute(String, String);
-    method public void setHost(java.net.InetAddress);
-    method public void setPort(int);
-    method public void setServiceName(String);
-    method public void setServiceType(String);
-    method public void writeToParcel(android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.net.nsd.NsdServiceInfo> CREATOR;
-  }
-
-}
-
 package android.net.rtp {
 
   @Deprecated public class AudioCodec {
@@ -27349,6 +27904,7 @@
     field public static final String CATEGORY_PAYMENT = "payment";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
+    field public static final String EXTRA_USERID = "android.nfc.cardemulation.extra.USERID";
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
@@ -30794,6 +31350,8 @@
     field public static final int Q = 29; // 0x1d
     field public static final int R = 30; // 0x1e
     field public static final int S = 31; // 0x1f
+    field public static final int S_V2 = 32; // 0x20
+    field public static final int TIRAMISU = 10000; // 0x2710
   }
 
   public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable {
@@ -31389,6 +31947,8 @@
     method @Nullable public double[] createDoubleArray();
     method @Nullable public float[] createFloatArray();
     method @Nullable public int[] createIntArray();
+    method @Nullable public <T extends android.os.IInterface> T[] createInterfaceArray(@NonNull java.util.function.IntFunction<T[]>, @NonNull java.util.function.Function<android.os.IBinder,T>);
+    method @Nullable public <T extends android.os.IInterface> java.util.ArrayList<T> createInterfaceArrayList(@NonNull java.util.function.Function<android.os.IBinder,T>);
     method @Nullable public long[] createLongArray();
     method @Nullable public String[] createStringArray();
     method @Nullable public java.util.ArrayList<java.lang.String> createStringArrayList();
@@ -31401,13 +31961,19 @@
     method public int dataPosition();
     method public int dataSize();
     method public void enforceInterface(@NonNull String);
+    method public void enforceNoDataAvail();
     method public boolean hasFileDescriptors();
+    method public boolean hasFileDescriptors(int, int);
     method public byte[] marshall();
     method @NonNull public static android.os.Parcel obtain();
-    method @Nullable public Object[] readArray(@Nullable ClassLoader);
-    method @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+    method @NonNull public static android.os.Parcel obtain(@NonNull android.os.IBinder);
+    method @Deprecated @Nullable public Object[] readArray(@Nullable ClassLoader);
+    method @Nullable public <T> T[] readArray(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Deprecated @Nullable public java.util.ArrayList readArrayList(@Nullable ClassLoader);
+    method @Nullable public <T> java.util.ArrayList<T> readArrayList(@Nullable ClassLoader, @NonNull Class<? extends T>);
     method public void readBinderArray(@NonNull android.os.IBinder[]);
     method public void readBinderList(@NonNull java.util.List<android.os.IBinder>);
+    method @Nullable public byte[] readBlob();
     method public boolean readBoolean();
     method public void readBooleanArray(@NonNull boolean[]);
     method @Nullable public android.os.Bundle readBundle();
@@ -31422,23 +31988,34 @@
     method public android.os.ParcelFileDescriptor readFileDescriptor();
     method public float readFloat();
     method public void readFloatArray(@NonNull float[]);
-    method @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
+    method @Deprecated @Nullable public java.util.HashMap readHashMap(@Nullable ClassLoader);
+    method @Nullable public <K, V> java.util.HashMap<K,V> readHashMap(@Nullable ClassLoader, @NonNull Class<? extends K>, @NonNull Class<? extends V>);
     method public int readInt();
     method public void readIntArray(@NonNull int[]);
-    method public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+    method public <T extends android.os.IInterface> void readInterfaceArray(@NonNull T[], @NonNull java.util.function.Function<android.os.IBinder,T>);
+    method public <T extends android.os.IInterface> void readInterfaceList(@NonNull java.util.List<T>, @NonNull java.util.function.Function<android.os.IBinder,T>);
+    method @Deprecated public void readList(@NonNull java.util.List, @Nullable ClassLoader);
+    method public <T> void readList(@NonNull java.util.List<? super T>, @Nullable ClassLoader, @NonNull Class<T>);
     method public long readLong();
     method public void readLongArray(@NonNull long[]);
-    method public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
-    method @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
-    method @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
-    method @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
-    method @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+    method @Deprecated public void readMap(@NonNull java.util.Map, @Nullable ClassLoader);
+    method public <K, V> void readMap(@NonNull java.util.Map<? super K,? super V>, @Nullable ClassLoader, @NonNull Class<K>, @NonNull Class<V>);
+    method @Deprecated @Nullable public <T extends android.os.Parcelable> T readParcelable(@Nullable ClassLoader);
+    method @Nullable public <T> T readParcelable(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Deprecated @Nullable public android.os.Parcelable[] readParcelableArray(@Nullable ClassLoader);
+    method @Nullable public <T> T[] readParcelableArray(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Deprecated @Nullable public android.os.Parcelable.Creator<?> readParcelableCreator(@Nullable ClassLoader);
+    method @Nullable public <T> android.os.Parcelable.Creator<T> readParcelableCreator(@Nullable ClassLoader, @NonNull Class<T>);
+    method @Deprecated @NonNull public <T extends android.os.Parcelable> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader);
+    method @NonNull public <T> java.util.List<T> readParcelableList(@NonNull java.util.List<T>, @Nullable ClassLoader, @NonNull Class<T>);
     method @Nullable public android.os.PersistableBundle readPersistableBundle();
     method @Nullable public android.os.PersistableBundle readPersistableBundle(@Nullable ClassLoader);
-    method @Nullable public java.io.Serializable readSerializable();
+    method @Deprecated @Nullable public java.io.Serializable readSerializable();
+    method @Nullable public <T> T readSerializable(@Nullable ClassLoader, @NonNull Class<T>);
     method @NonNull public android.util.Size readSize();
     method @NonNull public android.util.SizeF readSizeF();
-    method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
+    method @Deprecated @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader);
+    method @Nullable public <T> android.util.SparseArray<T> readSparseArray(@Nullable ClassLoader, @NonNull Class<? extends T>);
     method @Nullable public android.util.SparseBooleanArray readSparseBooleanArray();
     method @Nullable public String readString();
     method public void readStringArray(@NonNull String[]);
@@ -31456,6 +32033,8 @@
     method public void writeArray(@Nullable Object[]);
     method public void writeBinderArray(@Nullable android.os.IBinder[]);
     method public void writeBinderList(@Nullable java.util.List<android.os.IBinder>);
+    method public void writeBlob(@Nullable byte[]);
+    method public void writeBlob(@Nullable byte[], int, int);
     method public void writeBoolean(boolean);
     method public void writeBooleanArray(@Nullable boolean[]);
     method public void writeBundle(@Nullable android.os.Bundle);
@@ -31471,6 +32050,8 @@
     method public void writeFloatArray(@Nullable float[]);
     method public void writeInt(int);
     method public void writeIntArray(@Nullable int[]);
+    method public <T extends android.os.IInterface> void writeInterfaceArray(@Nullable T[]);
+    method public <T extends android.os.IInterface> void writeInterfaceList(@Nullable java.util.List<T>);
     method public void writeInterfaceToken(@NonNull String);
     method public void writeList(@Nullable java.util.List);
     method public void writeLong(long);
@@ -31570,7 +32151,7 @@
 
   public interface Parcelable {
     method public int describeContents();
-    method public void writeToParcel(android.os.Parcel, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
     field public static final int CONTENTS_FILE_DESCRIPTOR = 1; // 0x1
     field public static final int PARCELABLE_WRITE_RETURN_VALUE = 1; // 0x1
   }
@@ -31636,6 +32217,7 @@
     method public float getThermalHeadroom(@IntRange(from=0, to=60) int);
     method public boolean isBatteryDischargePredictionPersonalized();
     method public boolean isDeviceIdleMode();
+    method public boolean isDeviceLightIdleMode();
     method public boolean isIgnoringBatteryOptimizations(String);
     method public boolean isInteractive();
     method public boolean isPowerSaveMode();
@@ -31648,6 +32230,7 @@
     method public void removeThermalStatusListener(@NonNull android.os.PowerManager.OnThermalStatusChangedListener);
     field public static final int ACQUIRE_CAUSES_WAKEUP = 268435456; // 0x10000000
     field public static final String ACTION_DEVICE_IDLE_MODE_CHANGED = "android.os.action.DEVICE_IDLE_MODE_CHANGED";
+    field public static final String ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED = "android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED";
     field public static final String ACTION_POWER_SAVE_MODE_CHANGED = "android.os.action.POWER_SAVE_MODE_CHANGED";
     field @Deprecated public static final int FULL_WAKE_LOCK = 26; // 0x1a
     field public static final int LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF = 2; // 0x2
@@ -31689,8 +32272,10 @@
     method public static final long getElapsedCpuTime();
     method public static final int[] getExclusiveCores();
     method public static final int getGidForName(String);
-    method public static final long getStartElapsedRealtime();
-    method public static final long getStartUptimeMillis();
+    method public static long getStartElapsedRealtime();
+    method public static long getStartRequestedElapsedRealtime();
+    method public static long getStartRequestedUptimeMillis();
+    method public static long getStartUptimeMillis();
     method public static final int getThreadPriority(int) throws java.lang.IllegalArgumentException;
     method public static final int getUidForName(String);
     method public static final boolean is64Bit();
@@ -31698,6 +32283,7 @@
     method public static final boolean isIsolated();
     method public static final void killProcess(int);
     method public static final int myPid();
+    method @NonNull public static String myProcessName();
     method public static final int myTid();
     method public static final int myUid();
     method public static android.os.UserHandle myUserHandle();
@@ -31787,6 +32373,7 @@
     method public void close();
     method @NonNull public static android.os.SharedMemory create(@Nullable String, int) throws android.system.ErrnoException;
     method public int describeContents();
+    method @NonNull public static android.os.SharedMemory fromFileDescriptor(@NonNull android.os.ParcelFileDescriptor);
     method public int getSize();
     method @NonNull public java.nio.ByteBuffer map(int, int, int) throws android.system.ErrnoException;
     method @NonNull public java.nio.ByteBuffer mapReadOnly() throws android.system.ErrnoException;
@@ -31959,7 +32546,7 @@
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS"}) public int getUserCount();
     method public long getUserCreationTime(android.os.UserHandle);
     method public android.os.UserHandle getUserForSerialNumber(long);
-    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED, "android.permission.CREATE_USERS"}, conditional=true) public String getUserName();
+    method @NonNull @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.CREATE_USERS", "android.permission.QUERY_USERS", android.Manifest.permission.GET_ACCOUNTS_PRIVILEGED}) public String getUserName();
     method public java.util.List<android.os.UserHandle> getUserProfiles();
     method public android.os.Bundle getUserRestrictions();
     method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
@@ -31985,6 +32572,7 @@
     field public static final String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field @Deprecated public static final String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final String DISALLOW_ADD_USER = "no_add_user";
+    field public static final String DISALLOW_ADD_WIFI_CONFIG = "no_add_wifi_config";
     field public static final String DISALLOW_ADJUST_VOLUME = "no_adjust_volume";
     field public static final String DISALLOW_AIRPLANE_MODE = "no_airplane_mode";
     field public static final String DISALLOW_AMBIENT_DISPLAY = "no_ambient_display";
@@ -31993,6 +32581,7 @@
     field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
     field public static final String DISALLOW_CAMERA_TOGGLE = "disallow_camera_toggle";
+    field public static final String DISALLOW_CHANGE_WIFI_STATE = "no_change_wifi_state";
     field public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
     field public static final String DISALLOW_CONFIG_CELL_BROADCASTS = "no_config_cell_broadcasts";
@@ -32031,6 +32620,7 @@
     field public static final String DISALLOW_SET_WALLPAPER = "no_set_wallpaper";
     field public static final String DISALLOW_SHARE_INTO_MANAGED_PROFILE = "no_sharing_into_profile";
     field public static final String DISALLOW_SHARE_LOCATION = "no_share_location";
+    field public static final String DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI = "no_sharing_admin_configured_wifi";
     field public static final String DISALLOW_SMS = "no_sms";
     field public static final String DISALLOW_SYSTEM_ERROR_DIALOGS = "no_system_error_dialogs";
     field public static final String DISALLOW_UNIFIED_PASSWORD = "no_unified_password";
@@ -32038,6 +32628,8 @@
     field public static final String DISALLOW_UNMUTE_MICROPHONE = "no_unmute_microphone";
     field public static final String DISALLOW_USB_FILE_TRANSFER = "no_usb_file_transfer";
     field public static final String DISALLOW_USER_SWITCH = "no_user_switch";
+    field public static final String DISALLOW_WIFI_DIRECT = "no_wifi_direct";
+    field public static final String DISALLOW_WIFI_TETHERING = "no_wifi_tethering";
     field public static final String ENSURE_VERIFY_APPS = "ensure_verify_apps";
     field public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
     field public static final int QUIET_MODE_DISABLE_ONLY_IF_CREDENTIAL_NOT_REQUIRED = 1; // 0x1
@@ -32057,6 +32649,7 @@
   }
 
   public final class VibrationAttributes implements android.os.Parcelable {
+    method @NonNull public static android.os.VibrationAttributes createForUsage(int);
     method public int describeContents();
     method public int getFlags();
     method public int getUsage();
@@ -32065,13 +32658,16 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationAttributes> CREATOR;
     field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1; // 0x1
+    field public static final int USAGE_ACCESSIBILITY = 66; // 0x42
     field public static final int USAGE_ALARM = 17; // 0x11
     field public static final int USAGE_CLASS_ALARM = 1; // 0x1
     field public static final int USAGE_CLASS_FEEDBACK = 2; // 0x2
     field public static final int USAGE_CLASS_MASK = 15; // 0xf
+    field public static final int USAGE_CLASS_MEDIA = 3; // 0x3
     field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0
     field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41
     field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32
+    field public static final int USAGE_MEDIA = 19; // 0x13
     field public static final int USAGE_NOTIFICATION = 49; // 0x31
     field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22
     field public static final int USAGE_RINGTONE = 33; // 0x21
@@ -32082,6 +32678,7 @@
   public static final class VibrationAttributes.Builder {
     ctor public VibrationAttributes.Builder();
     ctor public VibrationAttributes.Builder(@Nullable android.os.VibrationAttributes);
+    ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes);
     method @NonNull public android.os.VibrationAttributes build();
     method @NonNull public android.os.VibrationAttributes.Builder setFlags(int, int);
     method @NonNull public android.os.VibrationAttributes.Builder setUsage(int);
@@ -32132,7 +32729,8 @@
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long[], int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(long[], int, android.media.AudioAttributes);
     method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect);
-    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(android.os.VibrationEffect, android.media.AudioAttributes);
+    method @RequiresPermission(android.Manifest.permission.VIBRATE) public void vibrate(@NonNull android.os.VibrationEffect, @NonNull android.os.VibrationAttributes);
     field public static final int VIBRATION_EFFECT_SUPPORT_NO = 2; // 0x2
     field public static final int VIBRATION_EFFECT_SUPPORT_UNKNOWN = 0; // 0x0
     field public static final int VIBRATION_EFFECT_SUPPORT_YES = 1; // 0x1
@@ -34786,6 +35384,8 @@
   }
 
   public static final class ContactsContract.Settings implements android.provider.ContactsContract.SettingsColumns {
+    method @Nullable public static android.accounts.Account getDefaultAccount(@NonNull android.content.ContentResolver);
+    field public static final String ACTION_SET_DEFAULT_ACCOUNT = "android.provider.action.SET_DEFAULT_ACCOUNT";
     field public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/setting";
     field public static final String CONTENT_TYPE = "vnd.android.cursor.dir/setting";
     field public static final android.net.Uri CONTENT_URI;
@@ -35115,10 +35715,12 @@
     field public static final String ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
     field public static final String ACTION_ADD_ACCOUNT = "android.settings.ADD_ACCOUNT_SETTINGS";
     field public static final String ACTION_AIRPLANE_MODE_SETTINGS = "android.settings.AIRPLANE_MODE_SETTINGS";
+    field public static final String ACTION_ALL_APPS_NOTIFICATION_SETTINGS = "android.settings.ALL_APPS_NOTIFICATION_SETTINGS";
     field public static final String ACTION_APN_SETTINGS = "android.settings.APN_SETTINGS";
     field public static final String ACTION_APPLICATION_DETAILS_SETTINGS = "android.settings.APPLICATION_DETAILS_SETTINGS";
     field public static final String ACTION_APPLICATION_DEVELOPMENT_SETTINGS = "android.settings.APPLICATION_DEVELOPMENT_SETTINGS";
     field public static final String ACTION_APPLICATION_SETTINGS = "android.settings.APPLICATION_SETTINGS";
+    field public static final String ACTION_APP_LOCALE_SETTINGS = "android.settings.APP_LOCALE_SETTINGS";
     field public static final String ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS = "android.settings.APP_NOTIFICATION_BUBBLE_SETTINGS";
     field public static final String ACTION_APP_NOTIFICATION_SETTINGS = "android.settings.APP_NOTIFICATION_SETTINGS";
     field public static final String ACTION_APP_OPEN_BY_DEFAULT_SETTINGS = "android.settings.APP_OPEN_BY_DEFAULT_SETTINGS";
@@ -35155,6 +35757,7 @@
     field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
     field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
     field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
+    field public static final String ACTION_MANAGE_SUPERVISOR_RESTRICTED_SETTING = "android.settings.MANAGE_SUPERVISOR_RESTRICTED_SETTING";
     field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
     field public static final String ACTION_MANAGE_WRITE_SETTINGS = "android.settings.action.MANAGE_WRITE_SETTINGS";
     field public static final String ACTION_MEMORY_CARD_SETTINGS = "android.settings.MEMORY_CARD_SETTINGS";
@@ -35179,6 +35782,7 @@
     field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS";
     field public static final String ACTION_SECURITY_SETTINGS = "android.settings.SECURITY_SETTINGS";
     field public static final String ACTION_SETTINGS = "android.settings.SETTINGS";
+    field public static final String ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY = "android.settings.SETTINGS_EMBED_DEEP_LINK_ACTIVITY";
     field public static final String ACTION_SHOW_REGULATORY_INFO = "android.settings.SHOW_REGULATORY_INFO";
     field public static final String ACTION_SHOW_WORK_POLICY_INFO = "android.settings.SHOW_WORK_POLICY_INFO";
     field public static final String ACTION_SOUND_SETTINGS = "android.settings.SOUND_SETTINGS";
@@ -35219,11 +35823,16 @@
     field public static final String EXTRA_EASY_CONNECT_ERROR_CODE = "android.provider.extra.EASY_CONNECT_ERROR_CODE";
     field public static final String EXTRA_INPUT_METHOD_ID = "input_method_id";
     field public static final String EXTRA_NOTIFICATION_LISTENER_COMPONENT_NAME = "android.provider.extra.NOTIFICATION_LISTENER_COMPONENT_NAME";
+    field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY";
+    field public static final String EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI = "android.provider.extra.SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI";
     field public static final String EXTRA_SUB_ID = "android.provider.extra.SUB_ID";
+    field public static final String EXTRA_SUPERVISOR_RESTRICTED_SETTING_KEY = "android.provider.extra.SUPERVISOR_RESTRICTED_SETTING_KEY";
     field public static final String EXTRA_WIFI_NETWORK_LIST = "android.provider.extra.WIFI_NETWORK_LIST";
     field public static final String EXTRA_WIFI_NETWORK_RESULT_LIST = "android.provider.extra.WIFI_NETWORK_RESULT_LIST";
     field public static final String INTENT_CATEGORY_USAGE_ACCESS_CONFIG = "android.intent.category.USAGE_ACCESS_CONFIG";
     field public static final String METADATA_USAGE_ACCESS_REASON = "android.settings.metadata.USAGE_ACCESS_REASON";
+    field public static final String SUPERVISOR_VERIFICATION_SETTING_BIOMETRICS = "supervisor_restricted_biometrics_controller";
+    field public static final String SUPERVISOR_VERIFICATION_SETTING_UNKNOWN = "";
   }
 
   public static final class Settings.Global extends android.provider.Settings.NameValueTable {
@@ -35245,7 +35854,7 @@
     field public static final String AIRPLANE_MODE_RADIOS = "airplane_mode_radios";
     field public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities";
     field public static final String ANIMATOR_DURATION_SCALE = "animator_duration_scale";
-    field public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
+    field @Deprecated public static final String APPLY_RAMPING_RINGER = "apply_ramping_ringer";
     field public static final String AUTO_TIME = "auto_time";
     field public static final String AUTO_TIME_ZONE = "auto_time_zone";
     field public static final String BLUETOOTH_ON = "bluetooth_on";
@@ -37278,6 +37887,28 @@
     method @Deprecated @NonNull public android.security.KeyPairGeneratorSpec.Builder setSubject(@NonNull javax.security.auth.x500.X500Principal);
   }
 
+  public class KeyStoreException extends java.lang.Exception {
+    method public int getNumericErrorCode();
+    method public boolean isSystemError();
+    method public boolean isTransientFailure();
+    method public boolean requiresUserAuthentication();
+    field public static final int ERROR_ATTESTATION_CHALLENGE_TOO_LARGE = 9; // 0x9
+    field public static final int ERROR_ID_ATTESTATION_FAILURE = 8; // 0x8
+    field public static final int ERROR_INCORRECT_USAGE = 13; // 0xd
+    field public static final int ERROR_INTERNAL_SYSTEM_ERROR = 4; // 0x4
+    field public static final int ERROR_KEYMINT_FAILURE = 10; // 0xa
+    field public static final int ERROR_KEYSTORE_FAILURE = 11; // 0xb
+    field public static final int ERROR_KEYSTORE_UNINITIALIZED = 3; // 0x3
+    field public static final int ERROR_KEY_CORRUPTED = 7; // 0x7
+    field public static final int ERROR_KEY_DOES_NOT_EXIST = 6; // 0x6
+    field public static final int ERROR_KEY_NOT_TEMPORALLY_VALID = 14; // 0xe
+    field public static final int ERROR_KEY_OPERATION_EXPIRED = 15; // 0xf
+    field public static final int ERROR_OTHER = 1; // 0x1
+    field public static final int ERROR_PERMISSION_DENIED = 5; // 0x5
+    field public static final int ERROR_UNIMPLEMENTED = 12; // 0xc
+    field public static final int ERROR_USER_AUTHENTICATION_REQUIRED = 2; // 0x2
+  }
+
   @Deprecated public final class KeyStoreParameter implements java.security.KeyStore.ProtectionParameter {
     method @Deprecated public boolean isEncryptionRequired();
   }
@@ -38112,9 +38743,11 @@
 
   public abstract class CarrierService extends android.app.Service {
     ctor public CarrierService();
-    method public final void notifyCarrierNetworkChange(boolean);
+    method @Deprecated public final void notifyCarrierNetworkChange(boolean);
+    method public final void notifyCarrierNetworkChange(int, boolean);
     method @CallSuper public android.os.IBinder onBind(android.content.Intent);
-    method public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+    method @Deprecated public abstract android.os.PersistableBundle onLoadConfig(android.service.carrier.CarrierIdentifier);
+    method @Nullable public android.os.PersistableBundle onLoadConfig(int, @Nullable android.service.carrier.CarrierIdentifier);
     field public static final String CARRIER_SERVICE_INTERFACE = "android.service.carrier.CarrierService";
   }
 
@@ -38169,6 +38802,7 @@
     method @NonNull public CharSequence getSubtitle();
     method @NonNull public CharSequence getTitle();
     method @Nullable public CharSequence getZone();
+    method public boolean isAuthRequired();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.service.controls.Control> CREATOR;
     field public static final int STATUS_DISABLED = 4; // 0x4
@@ -38183,6 +38817,7 @@
     ctor public Control.StatefulBuilder(@NonNull android.service.controls.Control);
     method @NonNull public android.service.controls.Control build();
     method @NonNull public android.service.controls.Control.StatefulBuilder setAppIntent(@NonNull android.app.PendingIntent);
+    method @NonNull public android.service.controls.Control.StatefulBuilder setAuthRequired(boolean);
     method @NonNull public android.service.controls.Control.StatefulBuilder setControlId(@NonNull String);
     method @NonNull public android.service.controls.Control.StatefulBuilder setControlTemplate(@NonNull android.service.controls.templates.ControlTemplate);
     method @NonNull public android.service.controls.Control.StatefulBuilder setCustomColor(@Nullable android.content.res.ColorStateList);
@@ -38944,6 +39579,13 @@
 
 package android.service.voice {
 
+  public final class VisibleActivityInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.service.voice.VoiceInteractionSession.ActivityId getActivityId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.VisibleActivityInfo> CREATOR;
+  }
+
   public class VoiceInteractionService extends android.app.Service {
     ctor public VoiceInteractionService();
     method public int getDisabledShowContext();
@@ -39005,6 +39647,7 @@
     method public void onTaskStarted(android.content.Intent, int);
     method public void onTrimMemory(int);
     method public final void performDirectAction(@NonNull android.app.DirectAction, @Nullable android.os.Bundle, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.os.Bundle>);
+    method public final void registerVisibleActivityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
     method public final void requestDirectActions(@NonNull android.service.voice.VoiceInteractionSession.ActivityId, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.app.DirectAction>>);
     method public void setContentView(android.view.View);
     method public void setDisabledShowContext(int);
@@ -39014,6 +39657,7 @@
     method public void show(android.os.Bundle, int);
     method public void startAssistantActivity(android.content.Intent);
     method public void startVoiceActivity(android.content.Intent);
+    method public final void unregisterVisibleActivityCallback(@NonNull android.service.voice.VoiceInteractionSession.VisibleActivityCallback);
     field public static final int SHOW_SOURCE_ACTIVITY = 16; // 0x10
     field public static final int SHOW_SOURCE_APPLICATION = 8; // 0x8
     field public static final int SHOW_SOURCE_ASSIST_GESTURE = 4; // 0x4
@@ -39087,6 +39731,11 @@
     method public boolean isActive();
   }
 
+  public static interface VoiceInteractionSession.VisibleActivityCallback {
+    method public default void onInvisible(@NonNull android.service.voice.VoiceInteractionSession.ActivityId);
+    method public default void onVisible(@NonNull android.service.voice.VisibleActivityInfo);
+  }
+
   public abstract class VoiceInteractionSessionService extends android.app.Service {
     ctor public VoiceInteractionSessionService();
     method public android.os.IBinder onBind(android.content.Intent);
@@ -39581,6 +40230,7 @@
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
+    field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 268435456; // 0x10000000
     field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
     field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
     field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
@@ -39646,6 +40296,7 @@
     method public final void cancelCall();
     method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
     method public abstract void onPlaceCall(@NonNull android.net.Uri, @NonNull android.telecom.PhoneAccountHandle, boolean);
+    method public void onRedirectionTimeout();
     method public final boolean onUnbind(@NonNull android.content.Intent);
     method public final void placeCallUnmodified();
     method public final void redirectCall(@NonNull android.net.Uri, @NonNull android.telecom.PhoneAccountHandle, boolean);
@@ -39866,6 +40517,7 @@
     field public static final int CAPABILITY_MANAGE_CONFERENCE = 128; // 0x80
     field public static final int CAPABILITY_MERGE_CONFERENCE = 4; // 0x4
     field public static final int CAPABILITY_MUTE = 64; // 0x40
+    field public static final int CAPABILITY_REMOTE_PARTY_SUPPORTS_RTT = 536870912; // 0x20000000
     field public static final int CAPABILITY_RESPOND_VIA_TEXT = 32; // 0x20
     field public static final int CAPABILITY_SEPARATE_FROM_CONFERENCE = 4096; // 0x1000
     field public static final int CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL = 768; // 0x300
@@ -39897,6 +40549,7 @@
     field public static final String EXTRA_CHILD_ADDRESS = "android.telecom.extra.CHILD_ADDRESS";
     field public static final String EXTRA_IS_RTT_AUDIO_PRESENT = "android.telecom.extra.IS_RTT_AUDIO_PRESENT";
     field public static final String EXTRA_LAST_FORWARDED_NUMBER = "android.telecom.extra.LAST_FORWARDED_NUMBER";
+    field public static final String EXTRA_LAST_KNOWN_CELL_IDENTITY = "android.telecom.extra.LAST_KNOWN_CELL_IDENTITY";
     field public static final String EXTRA_SIP_INVITE = "android.telecom.extra.SIP_INVITE";
     field public static final int PROPERTY_ASSISTED_DIALING = 512; // 0x200
     field public static final int PROPERTY_CROSS_SIM = 8192; // 0x2000
@@ -40332,7 +40985,7 @@
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
     method public String getDefaultDialerPackage();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telecom.PhoneAccountHandle getDefaultOutgoingPhoneAccount(String);
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}, conditional=true) public String getLine1Number(android.telecom.PhoneAccountHandle);
     method public android.telecom.PhoneAccount getPhoneAccount(android.telecom.PhoneAccountHandle);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telecom.PhoneAccountHandle> getSelfManagedPhoneAccounts();
     method public android.telecom.PhoneAccountHandle getSimCallManager();
@@ -40644,6 +41297,7 @@
     method @NonNull public java.util.List<java.lang.Integer> getBands();
     method @NonNull public java.util.List<java.lang.String> getMccMncs();
     method public int getPriority();
+    method @NonNull public java.util.List<android.telephony.RadioAccessSpecifier> getRadioAccessSpecifiers();
     method public int getSubId();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.AvailableNetworkInfo> CREATOR;
@@ -40652,6 +41306,14 @@
     field public static final int PRIORITY_MED = 2; // 0x2
   }
 
+  public static final class AvailableNetworkInfo.Builder {
+    ctor public AvailableNetworkInfo.Builder(int);
+    method @NonNull public android.telephony.AvailableNetworkInfo build();
+    method @NonNull public android.telephony.AvailableNetworkInfo.Builder setMccMncs(@NonNull java.util.List<java.lang.String>);
+    method @NonNull public android.telephony.AvailableNetworkInfo.Builder setPriority(int);
+    method @NonNull public android.telephony.AvailableNetworkInfo.Builder setRadioAccessSpecifiers(@NonNull java.util.List<android.telephony.RadioAccessSpecifier>);
+  }
+
   public final class BarringInfo implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
@@ -40685,11 +41347,11 @@
   }
 
   public class CarrierConfigManager {
-    method @Nullable public android.os.PersistableBundle getConfig();
-    method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
-    method @Nullable public android.os.PersistableBundle getConfigForSubId(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfig();
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.os.PersistableBundle getConfigForSubId(int);
     method public static boolean isConfigForIdentifiedCarrier(android.os.PersistableBundle);
-    method public void notifyConfigChangedForSubId(int);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyConfigChangedForSubId(int);
     field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
     field public static final int CARRIER_NR_AVAILABILITY_NSA = 1; // 0x1
     field public static final int CARRIER_NR_AVAILABILITY_SA = 2; // 0x2
@@ -40760,6 +41422,7 @@
     field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
     field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
     field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
+    field public static final String KEY_CARRIER_SUPPORTS_OPP_DATA_AUTO_PROVISIONING_BOOL = "carrier_supports_opp_data_auto_provisioning_bool";
     field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
     field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
     field public static final String KEY_CARRIER_USSD_METHOD_INT = "carrier_ussd_method_int";
@@ -40779,6 +41442,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+    field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
     field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
@@ -40818,9 +41482,12 @@
     field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
     field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
     field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
+    field public static final String KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL = "enable_cross_sim_calling_on_opportunistic_data_bool";
     field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
     field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool";
     field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int";
+    field public static final String KEY_ESIM_DOWNLOAD_RETRY_BACKOFF_TIMER_SEC_INT = "esim_download_retry_backoff_timer_sec_int";
+    field public static final String KEY_ESIM_MAX_DOWNLOAD_RETRY_ATTEMPTS_INT = "esim_max_download_retry_attempts_int";
     field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
     field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
     field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
@@ -40923,6 +41590,7 @@
     field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
     field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
     field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
+    field public static final String KEY_SMDP_SERVER_ADDRESS_STRING = "smdp_server_address_string";
     field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
     field public static final String KEY_SUPPORTS_CALL_COMPOSER_BOOL = "supports_call_composer_bool";
     field public static final String KEY_SUPPORTS_DEVICE_TO_DEVICE_COMMUNICATION_USING_DTMF_BOOL = "supports_device_to_device_communication_using_dtmf_bool";
@@ -42226,8 +42894,11 @@
     field public static final String MMS_CONFIG_UA_PROF_URL = "uaProfUrl";
     field public static final String MMS_CONFIG_USER_AGENT = "userAgent";
     field public static final int MMS_ERROR_CONFIGURATION_ERROR = 7; // 0x7
+    field public static final int MMS_ERROR_DATA_DISABLED = 11; // 0xb
     field public static final int MMS_ERROR_HTTP_FAILURE = 4; // 0x4
+    field public static final int MMS_ERROR_INACTIVE_SUBSCRIPTION = 10; // 0xa
     field public static final int MMS_ERROR_INVALID_APN = 2; // 0x2
+    field public static final int MMS_ERROR_INVALID_SUBSCRIPTION_ID = 9; // 0x9
     field public static final int MMS_ERROR_IO_ERROR = 5; // 0x5
     field public static final int MMS_ERROR_NO_DATA_NETWORK = 8; // 0x8
     field public static final int MMS_ERROR_RETRY = 6; // 0x6
@@ -42384,10 +43055,12 @@
     method @Nullable public String getMccString();
     method @Deprecated public int getMnc();
     method @Nullable public String getMncString();
-    method public String getNumber();
+    method @Deprecated public String getNumber();
+    method public int getPortIndex();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public int getSubscriptionType();
+    method public int getUsageSetting();
     method public boolean isEmbedded();
     method public boolean isOpportunistic();
     method public void writeToParcel(android.os.Parcel, int);
@@ -42417,6 +43090,8 @@
     method @NonNull public java.util.List<android.net.Uri> getDeviceToDeviceStatusSharingContacts(int);
     method public int getDeviceToDeviceStatusSharingPreference(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<android.telephony.SubscriptionInfo> getOpportunisticSubscriptions();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int, int);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_NUMBERS, "android.permission.READ_PRIVILEGED_PHONE_STATE", "carrier privileges"}) public String getPhoneNumber(int);
     method public static int getSlotIndex(int);
     method @Nullable public int[] getSubscriptionIds(int);
     method @NonNull public java.util.List<android.telephony.SubscriptionPlan> getSubscriptionPlans(int);
@@ -42428,6 +43103,7 @@
     method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
     method public void removeOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void removeSubscriptionsFromGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
+    method @RequiresPermission("carrier privileges") public void setCarrierPhoneNumber(int, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingContacts(int, @NonNull java.util.List<android.net.Uri>);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDeviceToDeviceStatusSharingPreference(int, int);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunistic(boolean, int);
@@ -42454,8 +43130,15 @@
     field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
     field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
     field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+    field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
+    field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
+    field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
     field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
     field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+    field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
+    field public static final int USAGE_SETTING_DEFAULT = 0; // 0x0
+    field public static final int USAGE_SETTING_UNKNOWN = -1; // 0xffffffff
+    field public static final int USAGE_SETTING_VOICE_CENTRIC = 1; // 0x1
   }
 
   public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
@@ -42619,11 +43302,11 @@
     method public int getCarrierIdFromSimMccMnc();
     method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();
     method public int getDataActivity();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getDataNetworkType();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getDataNetworkType();
     method public int getDataState();
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId();
     method @Deprecated @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getDeviceId(int);
-    method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getDeviceSoftwareVersion();
+    method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public String getDeviceSoftwareVersion();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getEmergencyNumberList(int);
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public java.util.List<java.lang.String> getEquivalentHomePlmns();
@@ -42632,7 +43315,7 @@
     method public String getIccAuthentication(int, int, String);
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei();
     method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public String getImei(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
+    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_SMS, android.Manifest.permission.READ_PHONE_NUMBERS}) public String getLine1Number();
     method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public String getManualNetworkSelectionPlmn();
     method @Nullable public String getManufacturerCode();
     method @Nullable public String getManufacturerCode(int);
@@ -42655,6 +43338,7 @@
     method public int getPhoneType();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription();
     method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState(boolean, boolean);
     method @Nullable public android.telephony.SignalStrength getSignalStrength();
     method public int getSimCarrierId();
     method @Nullable public CharSequence getSimCarrierIdName();
@@ -42676,26 +43360,26 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVisualVoicemailPackageName();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailAlphaTag();
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public String getVoiceMailNumber();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int getVoiceNetworkType();
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public int getVoiceNetworkType();
     method @Nullable public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
     method public boolean hasCarrierPrivileges();
     method public boolean hasIccCard();
-    method @Deprecated public boolean iccCloseLogicalChannel(int);
-    method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+    method public boolean iccCloseLogicalChannel(int);
+    method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
     method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
-    method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
-    method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
-    method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+    method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+    method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+    method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
     method public boolean isConcurrentVoiceAndDataSupported();
     method public boolean isDataCapable();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isDataConnectionAllowed();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabled();
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataEnabledForReason(int);
-    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataConnectionAllowed();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
-    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isModemEnabledForSlot(int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isModemEnabledForSlot(int);
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public int isMultiSimSupported();
     method public boolean isNetworkRoaming();
     method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
@@ -42707,17 +43391,19 @@
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
     method public void registerTelephonyCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
+    method public void registerTelephonyCallback(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
+    method @Nullable @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(boolean, @NonNull android.telephony.NetworkScanRequest, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyScanManager.NetworkScanCallback);
     method public void sendDialerSpecialCode(String);
-    method @Deprecated public String sendEnvelopeWithStatus(String);
+    method public String sendEnvelopeWithStatus(String);
     method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
     method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCallComposerStatus(int);
     method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabledForReason(int, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setForbiddenPlmns(@NonNull java.util.List<java.lang.String>);
-    method public boolean setLine1NumberForDisplay(String, String);
+    method @Deprecated public boolean setLine1NumberForDisplay(String, String);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setNetworkSelectionModeAutomatic();
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(String, boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setNetworkSelectionModeManual(@NonNull String, boolean, int);
@@ -42777,11 +43463,15 @@
     field public static final int DATA_DISCONNECTED = 0; // 0x0
     field public static final int DATA_DISCONNECTING = 4; // 0x4
     field public static final int DATA_ENABLED_REASON_CARRIER = 2; // 0x2
+    field public static final int DATA_ENABLED_REASON_OVERRIDE = 4; // 0x4
     field public static final int DATA_ENABLED_REASON_POLICY = 1; // 0x1
     field public static final int DATA_ENABLED_REASON_THERMAL = 3; // 0x3
+    field public static final int DATA_ENABLED_REASON_UNKNOWN = -1; // 0xffffffff
     field public static final int DATA_ENABLED_REASON_USER = 0; // 0x0
+    field public static final int DATA_HANDOVER_IN_PROGRESS = 5; // 0x5
     field public static final int DATA_SUSPENDED = 3; // 0x3
     field public static final int DATA_UNKNOWN = -1; // 0xffffffff
+    field public static final int DEFAULT_PORT_INDEX = 0; // 0x0
     field public static final int ERI_FLASH = 2; // 0x2
     field public static final int ERI_OFF = 1; // 0x1
     field public static final int ERI_ON = 0; // 0x0
@@ -42928,14 +43618,28 @@
     method public int describeContents();
     method public int getCardId();
     method @Nullable public String getEid();
-    method @Nullable public String getIccId();
-    method public int getSlotIndex();
+    method @Deprecated @Nullable public String getIccId();
+    method public int getPhysicalSlotIndex();
+    method @NonNull public java.util.Collection<android.telephony.UiccPortInfo> getPorts();
+    method @Deprecated public int getSlotIndex();
     method public boolean isEuicc();
+    method public boolean isMultipleEnabledProfilesSupported();
     method public boolean isRemovable();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccCardInfo> CREATOR;
   }
 
+  public final class UiccPortInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getIccId();
+    method @IntRange(from=0) public int getLogicalSlotIndex();
+    method @IntRange(from=0) public int getPortIndex();
+    method public boolean isActive();
+    method public void writeToParcel(@Nullable android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.telephony.UiccPortInfo> CREATOR;
+    field public static final String ICCID_REDACTED = "FFFFFFFFFFFFFFFFFFFF";
+  }
+
   public abstract class VisualVoicemailService extends android.app.Service {
     ctor public VisualVoicemailService();
     method public android.os.IBinder onBind(android.content.Intent);
@@ -43014,6 +43718,8 @@
     method public String getMmsProxyAddressAsString();
     method public int getMmsProxyPort();
     method public android.net.Uri getMmsc();
+    method public int getMtuV4();
+    method public int getMtuV6();
     method public int getMvnoType();
     method public int getNetworkTypeBitmask();
     method public String getOperatorNumeric();
@@ -43070,10 +43776,14 @@
     method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyAddress(@Nullable String);
     method @NonNull public android.telephony.data.ApnSetting.Builder setMmsProxyPort(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setMmsc(@Nullable android.net.Uri);
+    method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV4(int);
+    method @NonNull public android.telephony.data.ApnSetting.Builder setMtuV6(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setMvnoType(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setNetworkTypeBitmask(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setOperatorNumeric(@Nullable String);
     method @NonNull public android.telephony.data.ApnSetting.Builder setPassword(@Nullable String);
+    method @NonNull public android.telephony.data.ApnSetting.Builder setPersistent(boolean);
+    method @NonNull public android.telephony.data.ApnSetting.Builder setProfileId(int);
     method @NonNull public android.telephony.data.ApnSetting.Builder setProtocol(int);
     method @Deprecated public android.telephony.data.ApnSetting.Builder setProxyAddress(java.net.InetAddress);
     method @NonNull public android.telephony.data.ApnSetting.Builder setProxyAddress(@Nullable String);
@@ -43236,8 +43946,10 @@
     method @Nullable public String getEid();
     method @Nullable public android.telephony.euicc.EuiccInfo getEuiccInfo();
     method public boolean isEnabled();
+    method public boolean isSimPortAvailable(int);
     method public void startResolutionActivity(android.app.Activity, int, android.content.Intent, android.app.PendingIntent) throws android.content.IntentSender.SendIntentException;
-    method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+    method @Deprecated @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, android.app.PendingIntent);
+    method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void switchToSubscription(int, int, @NonNull android.app.PendingIntent);
     method @RequiresPermission("android.permission.WRITE_EMBEDDED_SUBSCRIPTIONS") public void updateSubscriptionNickname(int, @Nullable String, @NonNull android.app.PendingIntent);
     field public static final String ACTION_MANAGE_EMBEDDED_SUBSCRIPTIONS = "android.telephony.euicc.action.MANAGE_EMBEDDED_SUBSCRIPTIONS";
     field public static final String ACTION_NOTIFY_CARRIER_SETUP_INCOMPLETE = "android.telephony.euicc.action.NOTIFY_CARRIER_SETUP_INCOMPLETE";
@@ -43401,8 +44113,10 @@
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVoWiFiSettingEnabled();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public boolean isVtSettingEnabled();
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public void registerImsStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsStateCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void registerMmTelCapabilityCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    method public void unregisterImsStateCallback(@NonNull android.telephony.ims.ImsStateCallback);
     method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PRECISE_PHONE_STATE}) public void unregisterMmTelCapabilityCallback(@NonNull android.telephony.ims.ImsMmTelManager.CapabilityCallback);
     field public static final int WIFI_MODE_CELLULAR_PREFERRED = 1; // 0x1
     field public static final int WIFI_MODE_WIFI_ONLY = 0; // 0x0
@@ -43419,7 +44133,9 @@
     method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter();
     method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
+    method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE", "android.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE"}) public void registerImsStateCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ImsStateCallback) throws android.telephony.ims.ImsException;
     method @RequiresPermission(android.Manifest.permission.READ_PRECISE_PHONE_STATE) public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
+    method public void unregisterImsStateCallback(@NonNull android.telephony.ims.ImsStateCallback);
     field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN";
   }
 
@@ -43618,6 +44334,19 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.ImsRegistrationAttributes> CREATOR;
   }
 
+  public abstract class ImsStateCallback {
+    ctor public ImsStateCallback();
+    method public abstract void onAvailable();
+    method public abstract void onError();
+    method public abstract void onUnavailable(int);
+    field public static final int REASON_IMS_SERVICE_DISCONNECTED = 3; // 0x3
+    field public static final int REASON_IMS_SERVICE_NOT_READY = 6; // 0x6
+    field public static final int REASON_NO_IMS_SERVICE_CONFIGURED = 4; // 0x4
+    field public static final int REASON_SUBSCRIPTION_INACTIVE = 5; // 0x5
+    field public static final int REASON_UNKNOWN_PERMANENT_ERROR = 2; // 0x2
+    field public static final int REASON_UNKNOWN_TEMPORARY_ERROR = 1; // 0x1
+  }
+
   public class RcsUceAdapter {
     method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isUceSettingEnabled() throws android.telephony.ims.ImsException;
   }
@@ -44072,6 +44801,7 @@
     field public static final int TYPE_TEXT_FLAG_CAP_CHARACTERS = 4096; // 0x1000
     field public static final int TYPE_TEXT_FLAG_CAP_SENTENCES = 16384; // 0x4000
     field public static final int TYPE_TEXT_FLAG_CAP_WORDS = 8192; // 0x2000
+    field public static final int TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS = 1048576; // 0x100000
     field public static final int TYPE_TEXT_FLAG_IME_MULTI_LINE = 262144; // 0x40000
     field public static final int TYPE_TEXT_FLAG_MULTI_LINE = 131072; // 0x20000
     field public static final int TYPE_TEXT_FLAG_NO_SUGGESTIONS = 524288; // 0x80000
@@ -44150,8 +44880,10 @@
     field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
     field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
     field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
+    field public static final int HYPHENATION_FREQUENCY_FULL_FAST = 4; // 0x4
     field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
     field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
+    field public static final int HYPHENATION_FREQUENCY_NORMAL_FAST = 3; // 0x3
     field public static final int JUSTIFICATION_MODE_INTER_WORD = 1; // 0x1
     field public static final int JUSTIFICATION_MODE_NONE = 0; // 0x0
   }
@@ -45199,8 +45931,10 @@
 
   public class StyleSpan extends android.text.style.MetricAffectingSpan implements android.text.ParcelableSpan {
     ctor public StyleSpan(int);
+    ctor public StyleSpan(int, int);
     ctor public StyleSpan(@NonNull android.os.Parcel);
     method public int describeContents();
+    method public int getFontWeightAdjustment();
     method public int getSpanTypeId();
     method public int getStyle();
     method public void updateDrawState(android.text.TextPaint);
@@ -45218,6 +45952,17 @@
     method public void writeToParcel(android.os.Parcel, int);
   }
 
+  public final class SuggestionRangeSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
+    ctor public SuggestionRangeSpan();
+    method public int describeContents();
+    method public int getBackgroundColor();
+    method public int getSpanTypeId();
+    method public void setBackgroundColor(int);
+    method public void updateDrawState(@NonNull android.text.TextPaint);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.style.SuggestionRangeSpan> CREATOR;
+  }
+
   public class SuggestionSpan extends android.text.style.CharacterStyle implements android.text.ParcelableSpan {
     ctor public SuggestionSpan(android.content.Context, String[], int);
     ctor public SuggestionSpan(java.util.Locale, String[], int);
@@ -45569,7 +46314,7 @@
     method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
     method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter);
     method public static final boolean addLinks(@NonNull android.text.Spannable, @NonNull java.util.regex.Pattern, @Nullable String, @Nullable String[], @Nullable android.text.util.Linkify.MatchFilter, @Nullable android.text.util.Linkify.TransformFilter, @Nullable java.util.function.Function<java.lang.String,android.text.style.URLSpan>);
-    field public static final int ALL = 15; // 0xf
+    field @Deprecated public static final int ALL = 15; // 0xf
     field public static final int EMAIL_ADDRESSES = 2; // 0x2
     field @Deprecated public static final int MAP_ADDRESSES = 8; // 0x8
     field public static final int PHONE_NUMBERS = 4; // 0x4
@@ -45955,6 +46700,7 @@
     method public boolean contains(Object);
     method public boolean containsAll(java.util.Collection<?>);
     method public void ensureCapacity(int);
+    method public void forEach(java.util.function.Consumer<? super E>);
     method public int indexOf(Object);
     method public boolean isEmpty();
     method public java.util.Iterator<E> iterator();
@@ -46834,8 +47580,15 @@
   }
 
   @UiThread public interface AttachedSurfaceControl {
+    method public default void addOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
     method public boolean applyTransactionOnDraw(@NonNull android.view.SurfaceControl.Transaction);
     method @Nullable public android.view.SurfaceControl.Transaction buildReparentTransaction(@NonNull android.view.SurfaceControl);
+    method public default int getBufferTransformHint();
+    method public default void removeOnBufferTransformHintChangedListener(@NonNull android.view.AttachedSurfaceControl.OnBufferTransformHintChangedListener);
+  }
+
+  @UiThread public static interface AttachedSurfaceControl.OnBufferTransformHintChangedListener {
+    method public void onBufferTransformHintChanged(int);
   }
 
   public final class Choreographer {
@@ -47471,6 +48224,10 @@
     field public static final int KEYCODE_CUT = 277; // 0x115
     field public static final int KEYCODE_D = 32; // 0x20
     field public static final int KEYCODE_DEL = 67; // 0x43
+    field public static final int KEYCODE_DEMO_APP_1 = 301; // 0x12d
+    field public static final int KEYCODE_DEMO_APP_2 = 302; // 0x12e
+    field public static final int KEYCODE_DEMO_APP_3 = 303; // 0x12f
+    field public static final int KEYCODE_DEMO_APP_4 = 304; // 0x130
     field public static final int KEYCODE_DPAD_CENTER = 23; // 0x17
     field public static final int KEYCODE_DPAD_DOWN = 20; // 0x14
     field public static final int KEYCODE_DPAD_DOWN_LEFT = 269; // 0x10d
@@ -47502,6 +48259,10 @@
     field public static final int KEYCODE_F7 = 137; // 0x89
     field public static final int KEYCODE_F8 = 138; // 0x8a
     field public static final int KEYCODE_F9 = 139; // 0x8b
+    field public static final int KEYCODE_FEATURED_APP_1 = 297; // 0x129
+    field public static final int KEYCODE_FEATURED_APP_2 = 298; // 0x12a
+    field public static final int KEYCODE_FEATURED_APP_3 = 299; // 0x12b
+    field public static final int KEYCODE_FEATURED_APP_4 = 300; // 0x12c
     field public static final int KEYCODE_FOCUS = 80; // 0x50
     field public static final int KEYCODE_FORWARD = 125; // 0x7d
     field public static final int KEYCODE_FORWARD_DEL = 112; // 0x70
@@ -47667,6 +48428,14 @@
     field public static final int KEYCODE_U = 49; // 0x31
     field public static final int KEYCODE_UNKNOWN = 0; // 0x0
     field public static final int KEYCODE_V = 50; // 0x32
+    field public static final int KEYCODE_VIDEO_APP_1 = 289; // 0x121
+    field public static final int KEYCODE_VIDEO_APP_2 = 290; // 0x122
+    field public static final int KEYCODE_VIDEO_APP_3 = 291; // 0x123
+    field public static final int KEYCODE_VIDEO_APP_4 = 292; // 0x124
+    field public static final int KEYCODE_VIDEO_APP_5 = 293; // 0x125
+    field public static final int KEYCODE_VIDEO_APP_6 = 294; // 0x126
+    field public static final int KEYCODE_VIDEO_APP_7 = 295; // 0x127
+    field public static final int KEYCODE_VIDEO_APP_8 = 296; // 0x128
     field public static final int KEYCODE_VOICE_ASSIST = 231; // 0xe7
     field public static final int KEYCODE_VOLUME_DOWN = 25; // 0x19
     field public static final int KEYCODE_VOLUME_MUTE = 164; // 0xa4
@@ -48325,6 +49094,12 @@
     method public void readFromParcel(android.os.Parcel);
     method public void release();
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int BUFFER_TRANSFORM_IDENTITY = 0; // 0x0
+    field public static final int BUFFER_TRANSFORM_MIRROR_HORIZONTAL = 1; // 0x1
+    field public static final int BUFFER_TRANSFORM_MIRROR_VERTICAL = 2; // 0x2
+    field public static final int BUFFER_TRANSFORM_ROTATE_180 = 3; // 0x3
+    field public static final int BUFFER_TRANSFORM_ROTATE_270 = 7; // 0x7
+    field public static final int BUFFER_TRANSFORM_ROTATE_90 = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl> CREATOR;
   }
 
@@ -48333,6 +49108,7 @@
     method @NonNull public android.view.SurfaceControl build();
     method @NonNull public android.view.SurfaceControl.Builder setBufferSize(@IntRange(from=0) int, @IntRange(from=0) int);
     method @NonNull public android.view.SurfaceControl.Builder setFormat(int);
+    method @NonNull public android.view.SurfaceControl.Builder setHidden(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setName(@NonNull String);
     method @NonNull public android.view.SurfaceControl.Builder setOpaque(boolean);
     method @NonNull public android.view.SurfaceControl.Builder setParent(@Nullable android.view.SurfaceControl);
@@ -48340,17 +49116,25 @@
 
   public static class SurfaceControl.Transaction implements java.io.Closeable android.os.Parcelable {
     ctor public SurfaceControl.Transaction();
+    method @NonNull public android.view.SurfaceControl.Transaction addTransactionCommittedListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.TransactionCommittedListener);
     method public void apply();
     method public void close();
     method public int describeContents();
     method @NonNull public android.view.SurfaceControl.Transaction merge(@NonNull android.view.SurfaceControl.Transaction);
     method @NonNull public android.view.SurfaceControl.Transaction reparent(@NonNull android.view.SurfaceControl, @Nullable android.view.SurfaceControl);
     method @NonNull public android.view.SurfaceControl.Transaction setAlpha(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0, to=1.0) float);
+    method @NonNull public android.view.SurfaceControl.Transaction setBuffer(@NonNull android.view.SurfaceControl, @Nullable android.hardware.HardwareBuffer);
     method @NonNull public android.view.SurfaceControl.Transaction setBufferSize(@NonNull android.view.SurfaceControl, @IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setBufferTransform(@NonNull android.view.SurfaceControl, int);
+    method @NonNull public android.view.SurfaceControl.Transaction setCrop(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect);
+    method @NonNull public android.view.SurfaceControl.Transaction setDamageRegion(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Region);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int);
     method @NonNull public android.view.SurfaceControl.Transaction setFrameRate(@NonNull android.view.SurfaceControl, @FloatRange(from=0.0) float, int, int);
     method @NonNull public android.view.SurfaceControl.Transaction setGeometry(@NonNull android.view.SurfaceControl, @Nullable android.graphics.Rect, @Nullable android.graphics.Rect, int);
     method @NonNull public android.view.SurfaceControl.Transaction setLayer(@NonNull android.view.SurfaceControl, @IntRange(from=java.lang.Integer.MIN_VALUE, to=java.lang.Integer.MAX_VALUE) int);
+    method @NonNull public android.view.SurfaceControl.Transaction setOpaque(@NonNull android.view.SurfaceControl, boolean);
+    method @NonNull public android.view.SurfaceControl.Transaction setPosition(@NonNull android.view.SurfaceControl, float, float);
+    method @NonNull public android.view.SurfaceControl.Transaction setScale(@NonNull android.view.SurfaceControl, float, float);
     method @NonNull public android.view.SurfaceControl.Transaction setVisibility(@NonNull android.view.SurfaceControl, boolean);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.SurfaceControl.Transaction> CREATOR;
@@ -48466,6 +49250,10 @@
     field public static final int TO_RIGHT = 8; // 0x8
   }
 
+  public interface TransactionCommittedListener {
+    method public void onTransactionCommitted();
+  }
+
   public final class VelocityTracker {
     method public void addMovement(android.view.MotionEvent);
     method public void clear();
@@ -49187,6 +49975,7 @@
     field public static final int AUTOFILL_TYPE_NONE = 0; // 0x0
     field public static final int AUTOFILL_TYPE_TEXT = 1; // 0x1
     field public static final int AUTOFILL_TYPE_TOGGLE = 2; // 0x2
+    field public static final int DRAG_FLAG_ACCESSIBILITY_ACTION = 1024; // 0x400
     field public static final int DRAG_FLAG_GLOBAL = 256; // 0x100
     field public static final int DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION = 64; // 0x40
     field public static final int DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION = 128; // 0x80
@@ -50602,9 +51391,9 @@
     method public int getRecordCount();
     method public int getWindowChanges();
     method public void initFromParcel(android.os.Parcel);
-    method public static android.view.accessibility.AccessibilityEvent obtain(int);
-    method public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
-    method public static android.view.accessibility.AccessibilityEvent obtain();
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(int);
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain(android.view.accessibility.AccessibilityEvent);
+    method @Deprecated public static android.view.accessibility.AccessibilityEvent obtain();
     method public void setAction(int);
     method public void setContentChangeTypes(int);
     method public void setEventTime(long);
@@ -50613,6 +51402,9 @@
     method public void setPackageName(CharSequence);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+    field public static final int CONTENT_CHANGE_TYPE_DRAG_CANCELLED = 512; // 0x200
+    field public static final int CONTENT_CHANGE_TYPE_DRAG_DROPPED = 256; // 0x100
+    field public static final int CONTENT_CHANGE_TYPE_DRAG_STARTED = 128; // 0x80
     field public static final int CONTENT_CHANGE_TYPE_PANE_APPEARED = 16; // 0x10
     field public static final int CONTENT_CHANGE_TYPE_PANE_DISAPPEARED = 32; // 0x20
     field public static final int CONTENT_CHANGE_TYPE_PANE_TITLE = 8; // 0x8
@@ -50669,8 +51461,11 @@
 
   public final class AccessibilityManager {
     method public void addAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
+    method public void addAccessibilityServicesStateChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
+    method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
     method public void addAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener, @Nullable android.os.Handler);
+    method public void addAudioDescriptionRequestedChangeListener(@NonNull java.util.concurrent.Executor, @NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void addTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, @Nullable android.os.Handler);
     method @ColorInt public int getAccessibilityFocusColor();
@@ -50681,10 +51476,13 @@
     method public int getRecommendedTimeoutMillis(int, int);
     method public void interrupt();
     method public static boolean isAccessibilityButtonSupported();
+    method public boolean isAudioDescriptionRequested();
     method public boolean isEnabled();
     method public boolean isTouchExplorationEnabled();
     method public void removeAccessibilityRequestPreparer(android.view.accessibility.AccessibilityRequestPreparer);
+    method public boolean removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
     method public boolean removeAccessibilityStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener);
+    method public boolean removeAudioDescriptionRequestedChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener);
     method public boolean removeTouchExplorationStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener);
     method public void sendAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
     field public static final int FLAG_CONTENT_CONTROLS = 4; // 0x4
@@ -50692,10 +51490,18 @@
     field public static final int FLAG_CONTENT_TEXT = 2; // 0x2
   }
 
+  public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
+    method public void onAccessibilityServicesStateChanged(@NonNull android.view.accessibility.AccessibilityManager);
+  }
+
   public static interface AccessibilityManager.AccessibilityStateChangeListener {
     method public void onAccessibilityStateChanged(boolean);
   }
 
+  public static interface AccessibilityManager.AudioDescriptionRequestedChangeListener {
+    method public void onAudioDescriptionRequestedChanged(boolean);
+  }
+
   public static interface AccessibilityManager.TouchExplorationStateChangeListener {
     method public void onTouchExplorationStateChanged(boolean);
   }
@@ -50749,6 +51555,7 @@
     method @Nullable public android.view.accessibility.AccessibilityNodeInfo.TouchDelegateInfo getTouchDelegateInfo();
     method public android.view.accessibility.AccessibilityNodeInfo getTraversalAfter();
     method public android.view.accessibility.AccessibilityNodeInfo getTraversalBefore();
+    method @Nullable public String getUniqueId();
     method public String getViewIdResourceName();
     method public android.view.accessibility.AccessibilityWindowInfo getWindow();
     method public int getWindowId();
@@ -50774,13 +51581,13 @@
     method public boolean isShowingHintText();
     method public boolean isTextEntryKey();
     method public boolean isVisibleToUser();
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain();
-    method public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.View, int);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain();
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo obtain(android.view.accessibility.AccessibilityNodeInfo);
     method public boolean performAction(int);
     method public boolean performAction(int, android.os.Bundle);
-    method public void recycle();
+    method @Deprecated public void recycle();
     method public boolean refresh();
     method public boolean refreshWithExtraData(String, android.os.Bundle);
     method @Deprecated public void removeAction(int);
@@ -50843,6 +51650,7 @@
     method public void setTraversalAfter(android.view.View, int);
     method public void setTraversalBefore(android.view.View);
     method public void setTraversalBefore(android.view.View, int);
+    method public void setUniqueId(@Nullable String);
     method public void setViewIdResourceName(String);
     method public void setVisibleToUser(boolean);
     method public void writeToParcel(android.os.Parcel, int);
@@ -50911,6 +51719,9 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_COPY;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_CUT;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DISMISS;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_CANCEL;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_DROP;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_DRAG_START;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_EXPAND;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_FOCUS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_HIDE_TOOLTIP;
@@ -50939,7 +51750,12 @@
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_SELECTION;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SET_TEXT;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_ON_SCREEN;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_SUGGESTIONS;
     field public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SHOW_TOOLTIP;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_DOWN;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_LEFT;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_RIGHT;
+    field @NonNull public static final android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction ACTION_SWIPE_UP;
     field @NonNull public static final android.os.Parcelable.Creator<android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction> CREATOR;
   }
 
@@ -50950,8 +51766,8 @@
     method public int getRowCount();
     method public int getSelectionMode();
     method public boolean isHierarchical();
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionInfo obtain(int, int, boolean, int);
     field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
     field public static final int SELECTION_MODE_NONE = 0; // 0x0
     field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
@@ -50962,12 +51778,28 @@
     ctor public AccessibilityNodeInfo.CollectionItemInfo(int, int, int, int, boolean, boolean);
     method public int getColumnIndex();
     method public int getColumnSpan();
+    method @Nullable public String getColumnTitle();
     method public int getRowIndex();
     method public int getRowSpan();
+    method @Nullable public String getRowTitle();
     method @Deprecated public boolean isHeading();
     method public boolean isSelected();
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
-    method public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(int, int, int, int, boolean, boolean);
+    method @Deprecated @NonNull public static android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo obtain(@Nullable String, int, int, @Nullable String, int, int, boolean, boolean);
+  }
+
+  public static final class AccessibilityNodeInfo.CollectionItemInfo.Builder {
+    ctor public AccessibilityNodeInfo.CollectionItemInfo.Builder();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo build();
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnIndex(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnSpan(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setColumnTitle(@Nullable String);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setHeading(boolean);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowIndex(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowSpan(int);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setRowTitle(@Nullable String);
+    method @NonNull public android.view.accessibility.AccessibilityNodeInfo.CollectionItemInfo.Builder setSelected(boolean);
   }
 
   public static final class AccessibilityNodeInfo.ExtraRenderingInfo {
@@ -50982,7 +51814,7 @@
     method public float getMax();
     method public float getMin();
     method public int getType();
-    method public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
+    method @Deprecated public static android.view.accessibility.AccessibilityNodeInfo.RangeInfo obtain(int, float, float, float);
     field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
     field public static final int RANGE_TYPE_INT = 0; // 0x0
     field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
@@ -51016,6 +51848,7 @@
     method public CharSequence getClassName();
     method public CharSequence getContentDescription();
     method public int getCurrentItemIndex();
+    method public int getDisplayId();
     method public int getFromIndex();
     method public int getItemCount();
     method public int getMaxScrollX();
@@ -51035,9 +51868,9 @@
     method public boolean isFullScreen();
     method public boolean isPassword();
     method public boolean isScrollable();
-    method public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
-    method public static android.view.accessibility.AccessibilityRecord obtain();
-    method public void recycle();
+    method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain(android.view.accessibility.AccessibilityRecord);
+    method @Deprecated public static android.view.accessibility.AccessibilityRecord obtain();
+    method @Deprecated public void recycle();
     method public void setAddedCount(int);
     method public void setBeforeText(CharSequence);
     method public void setChecked(boolean);
@@ -51432,6 +52265,7 @@
 
   public final class AutofillManager {
     method public void cancel();
+    method public void clearAutofillRequestCallback();
     method public void commit();
     method public void disableAutofillServices();
     method @Nullable public android.content.ComponentName getAutofillServiceComponentName();
@@ -51457,6 +52291,7 @@
     method public void registerCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     method public void requestAutofill(@NonNull android.view.View);
     method public void requestAutofill(@NonNull android.view.View, int, @NonNull android.graphics.Rect);
+    method public void setAutofillRequestCallback(@NonNull java.util.concurrent.Executor, @NonNull android.view.autofill.AutofillRequestCallback);
     method public void setUserData(@Nullable android.service.autofill.UserData);
     method public void unregisterCallback(@Nullable android.view.autofill.AutofillManager.AutofillCallback);
     field public static final String EXTRA_ASSIST_STRUCTURE = "android.view.autofill.extra.ASSIST_STRUCTURE";
@@ -51475,6 +52310,10 @@
     field public static final int EVENT_INPUT_UNAVAILABLE = 3; // 0x3
   }
 
+  public interface AutofillRequestCallback {
+    method public void onFillRequest(@Nullable android.view.inputmethod.InlineSuggestionsRequest, @NonNull android.os.CancellationSignal, @NonNull android.service.autofill.FillCallback);
+  }
+
   public final class AutofillValue implements android.os.Parcelable {
     method public int describeContents();
     method public static android.view.autofill.AutofillValue forDate(long);
@@ -51845,10 +52684,12 @@
     ctor public InlineSuggestionsRequest.Builder(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder addInlinePresentationSpecs(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest build();
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setClientSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setExtras(@NonNull android.os.Bundle);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(@NonNull java.util.List<android.widget.inline.InlinePresentationSpec>);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setInlineTooltipPresentationSpec(@NonNull android.widget.inline.InlinePresentationSpec);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setMaxSuggestionCount(int);
+    method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setServiceSupported(boolean);
     method @NonNull public android.view.inputmethod.InlineSuggestionsRequest.Builder setSupportedLocales(@NonNull android.os.LocaleList);
   }
 
@@ -51879,13 +52720,14 @@
     method public boolean commitContent(@NonNull android.view.inputmethod.InputContentInfo, int, @Nullable android.os.Bundle);
     method public boolean commitCorrection(android.view.inputmethod.CorrectionInfo);
     method public boolean commitText(CharSequence, int);
+    method public default boolean commitText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean deleteSurroundingText(int, int);
     method public boolean deleteSurroundingTextInCodePoints(int, int);
     method public boolean endBatchEdit();
     method public boolean finishComposingText();
     method public int getCursorCapsMode(int);
     method public android.view.inputmethod.ExtractedText getExtractedText(android.view.inputmethod.ExtractedTextRequest, int);
-    method public android.os.Handler getHandler();
+    method @Nullable public android.os.Handler getHandler();
     method public CharSequence getSelectedText(int);
     method @Nullable public default android.view.inputmethod.SurroundingText getSurroundingText(@IntRange(from=0) int, @IntRange(from=0) int, int);
     method @Nullable public CharSequence getTextAfterCursor(@IntRange(from=0) int, int);
@@ -51898,9 +52740,12 @@
     method public boolean requestCursorUpdates(int);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
+    method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
     method public boolean setComposingText(CharSequence, int);
+    method public default boolean setComposingText(@NonNull CharSequence, int, @Nullable android.view.inputmethod.TextAttribute);
     method public default boolean setImeConsumesInput(boolean);
     method public boolean setSelection(int, int);
+    method @Nullable public default android.view.inputmethod.TextSnapshot takeSnapshot();
     field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
     field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
     field public static final int GET_EXTRACTED_TEXT_MONITOR = 1; // 0x1
@@ -51992,6 +52837,7 @@
     method public android.graphics.drawable.Drawable loadIcon(android.content.pm.PackageManager);
     method public CharSequence loadLabel(android.content.pm.PackageManager);
     method public boolean shouldShowInInputMethodPicker();
+    method public boolean supportsStylusHandwriting();
     method public boolean suppressesSpellChecker();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.InputMethodInfo> CREATOR;
@@ -52010,6 +52856,7 @@
     method public boolean hideSoftInputFromWindow(android.os.IBinder, int);
     method public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver);
     method @Deprecated public void hideStatusIcon(android.os.IBinder);
+    method public void invalidateInput(@NonNull android.view.View);
     method public boolean isAcceptingText();
     method public boolean isActive(android.view.View);
     method public boolean isActive();
@@ -52113,6 +52960,31 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
   }
 
+  public final class TextAttribute implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.os.PersistableBundle getExtras();
+    method @NonNull public java.util.List<java.lang.String> getTextConversionSuggestions();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAttribute> CREATOR;
+  }
+
+  public static final class TextAttribute.Builder {
+    ctor public TextAttribute.Builder();
+    method @NonNull public android.view.inputmethod.TextAttribute build();
+    method @NonNull public android.view.inputmethod.TextAttribute.Builder setExtras(@NonNull android.os.PersistableBundle);
+    method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>);
+  }
+
+  public final class TextSnapshot {
+    ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int);
+    method @IntRange(from=0xffffffff) public int getCompositionEnd();
+    method @IntRange(from=0xffffffff) public int getCompositionStart();
+    method public int getCursorCapsMode();
+    method @IntRange(from=0xffffffff) public int getSelectionEnd();
+    method @IntRange(from=0xffffffff) public int getSelectionStart();
+    method @NonNull public android.view.inputmethod.SurroundingText getSurroundingText();
+  }
+
 }
 
 package android.view.inspector {
@@ -53275,7 +54147,6 @@
     method public void onPermissionRequest(android.webkit.PermissionRequest);
     method public void onPermissionRequestCanceled(android.webkit.PermissionRequest);
     method public void onProgressChanged(android.webkit.WebView, int);
-    method @Deprecated public void onReachedMaxAppCacheSize(long, long, android.webkit.WebStorage.QuotaUpdater);
     method public void onReceivedIcon(android.webkit.WebView, android.graphics.Bitmap);
     method public void onReceivedTitle(android.webkit.WebView, String);
     method public void onReceivedTouchIconUrl(android.webkit.WebView, String, boolean);
@@ -53427,9 +54298,6 @@
     method public abstract void setAllowFileAccess(boolean);
     method @Deprecated public abstract void setAllowFileAccessFromFileURLs(boolean);
     method @Deprecated public abstract void setAllowUniversalAccessFromFileURLs(boolean);
-    method @Deprecated public abstract void setAppCacheEnabled(boolean);
-    method @Deprecated public abstract void setAppCacheMaxSize(long);
-    method @Deprecated public abstract void setAppCachePath(String);
     method public abstract void setBlockNetworkImage(boolean);
     method public abstract void setBlockNetworkLoads(boolean);
     method public abstract void setBuiltInZoomControls(boolean);
@@ -56806,6 +57674,8 @@
     method public void clearOnExitAnimationListener();
     method public void setOnExitAnimationListener(@NonNull android.window.SplashScreen.OnExitAnimationListener);
     method public void setSplashScreenTheme(@StyleRes int);
+    field public static final int SPLASH_SCREEN_STYLE_EMPTY = 0; // 0x0
+    field public static final int SPLASH_SCREEN_STYLE_ICON = 1; // 0x1
   }
 
   public static interface SplashScreen.OnExitAnimationListener {
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
index 2eafa93..8724b53 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/test-current.txt
@@ -26,6 +26,7 @@
     field public static final String MANAGE_ROLLBACKS = "android.permission.MANAGE_ROLLBACKS";
     field public static final String MANAGE_TOAST_RATE_LIMITING = "android.permission.MANAGE_TOAST_RATE_LIMITING";
     field public static final String MODIFY_REFRESH_RATE_SWITCHING_TYPE = "android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE";
+    field public static final String MODIFY_USER_PREFERRED_DISPLAY_MODE = "android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE";
     field public static final String NETWORK_SETTINGS = "android.permission.NETWORK_SETTINGS";
     field public static final String NETWORK_STACK = "android.permission.NETWORK_STACK";
     field public static final String OVERRIDE_DISPLAY_MODE_REQUESTS = "android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS";
@@ -41,6 +42,7 @@
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
+    field public static final String WRITE_COMMUNAL_STATE = "android.permission.WRITE_COMMUNAL_STATE";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
     field @Deprecated public static final String WRITE_MEDIA_STORAGE = "android.permission.WRITE_MEDIA_STORAGE";
     field public static final String WRITE_OBB = "android.permission.WRITE_OBB";
@@ -57,6 +59,7 @@
   public static final class R.bool {
     field public static final int config_assistantOnTopOfDream = 17891333; // 0x1110005
     field public static final int config_perDisplayFocusEnabled = 17891332; // 0x1110004
+    field public static final int config_preventImeStartupUnlessTextEditor;
     field public static final int config_remoteInsetsControllerControlsSystemBars = 17891334; // 0x1110006
   }
 
@@ -113,6 +116,7 @@
     method @RequiresPermission(android.Manifest.permission.RESET_APP_ERRORS) public void resetAppErrors();
     method public static void resumeAppSwitches() throws android.os.RemoteException;
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}) public void setStopUserOnSwitch(int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public boolean stopUser(int, boolean);
     method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
     method @RequiresPermission(android.Manifest.permission.DUMP) public void waitForBroadcastIdle();
@@ -127,6 +131,9 @@
     field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
     field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
     field public static final int PROCESS_STATE_TOP = 2; // 0x2
+    field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
+    field public static final int STOP_USER_ON_SWITCH_FALSE = 0; // 0x0
+    field public static final int STOP_USER_ON_SWITCH_TRUE = 1; // 0x1
   }
 
   public static class ActivityManager.RunningAppProcessInfo implements android.os.Parcelable {
@@ -140,11 +147,11 @@
   }
 
   public class ActivityOptions {
+    method @NonNull public static android.app.ActivityOptions fromBundle(@NonNull android.os.Bundle);
     method @NonNull public static android.app.ActivityOptions makeCustomAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method @NonNull @RequiresPermission(android.Manifest.permission.START_TASKS_FROM_RECENTS) public static android.app.ActivityOptions makeCustomTaskAnimation(@NonNull android.content.Context, int, int, @Nullable android.os.Handler, @Nullable android.app.ActivityOptions.OnAnimationStartedListener, @Nullable android.app.ActivityOptions.OnAnimationFinishedListener);
     method public static void setExitTransitionTimeout(long);
     method public void setLaunchActivityType(int);
-    method public void setLaunchTaskId(int);
     method public void setLaunchWindowingMode(int);
     method public void setLaunchedFromBubble(boolean);
     method public void setTaskAlwaysOnTop(boolean);
@@ -242,12 +249,15 @@
 
   public class BroadcastOptions {
     ctor public BroadcastOptions(@NonNull android.os.Bundle);
-    method public int getMaxManifestReceiverApiLevel();
+    method @Deprecated public int getMaxManifestReceiverApiLevel();
     method public long getTemporaryAppAllowlistDuration();
     method @Nullable public String getTemporaryAppAllowlistReason();
     method public int getTemporaryAppAllowlistReasonCode();
     method public int getTemporaryAppAllowlistType();
-    method public void setMaxManifestReceiverApiLevel(int);
+    method @Deprecated public void setMaxManifestReceiverApiLevel(int);
+    method public boolean testRequireCompatChange(int);
+    field public static final long CHANGE_ALWAYS_DISABLED = 210856463L; // 0xc916a0fL
+    field public static final long CHANGE_ALWAYS_ENABLED = 209888056L; // 0xc82a338L
   }
 
   public class DownloadManager {
@@ -256,15 +266,12 @@
 
   public class DreamManager {
     method @RequiresPermission(android.Manifest.permission.READ_DREAM_STATE) public boolean isDreaming();
-    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@NonNull android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setActiveDream(@Nullable android.content.ComponentName);
+    method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void setDreamOverlay(@Nullable android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void startDream(@NonNull android.content.ComponentName);
     method @RequiresPermission(android.Manifest.permission.WRITE_DREAM_STATE) public void stopDream();
   }
 
-  public final class GameManager {
-    method @RequiresPermission("android.permission.MANAGE_GAME_MODE") public void setGameMode(@NonNull String, int);
-  }
-
   public abstract class HomeVisibilityListener {
     ctor public HomeVisibilityListener();
     method public abstract void onHomeVisibilityChanged(boolean);
@@ -275,13 +282,17 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS, "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"}) public boolean setLock(int, @Nullable byte[], int, @Nullable byte[]);
   }
 
+  public class LocaleManager {
+    method @Nullable public android.os.LocaleList getSystemLocales();
+    method public void setSystemLocales(@NonNull android.os.LocaleList);
+  }
+
   public class Notification implements android.os.Parcelable {
     method public boolean shouldShowForegroundImmediately();
   }
 
   public final class NotificationChannel implements android.os.Parcelable {
     method public int getOriginalImportance();
-    method public boolean isBlockable();
     method public boolean isImportanceLockedByCriticalDeviceFunction();
     method public boolean isImportanceLockedByOEM();
     method public void lockFields(int);
@@ -303,21 +314,27 @@
 
   public class NotificationManager {
     method public void allowAssistantAdjustment(String);
+    method public void cleanUpCallersAfter(long);
     method public void disallowAssistantAdjustment(String);
     method public android.content.ComponentName getEffectsSuppressor();
     method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String);
-    method public boolean matchesCallFilter(android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean);
     method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel);
   }
 
   public final class PendingIntent implements android.os.Parcelable {
+    method public boolean addCancelListener(@NonNull java.util.concurrent.Executor, @NonNull android.app.PendingIntent.CancelListener);
     method @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public boolean intentFilterEquals(@Nullable android.app.PendingIntent);
     method @NonNull @RequiresPermission("android.permission.GET_INTENT_SENDER_INTENT") public java.util.List<android.content.pm.ResolveInfo> queryIntentComponents(int);
+    method public void removeCancelListener(@NonNull android.app.PendingIntent.CancelListener);
     field @Deprecated public static final int FLAG_MUTABLE_UNAUDITED = 33554432; // 0x2000000
   }
 
+  public static interface PendingIntent.CancelListener {
+    method public void onCanceled(@NonNull android.app.PendingIntent);
+  }
+
   public final class PictureInPictureParams implements android.os.Parcelable {
     method public java.util.List<android.app.RemoteAction> getActions();
     method public float getAspectRatio();
@@ -330,6 +347,7 @@
   }
 
   public class StatusBarManager {
+    method public void cancelRequestAddTile(@NonNull String);
     method public void clickNotification(@Nullable String, int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels();
     method public void expandNotificationsPanel();
@@ -339,6 +357,10 @@
     method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void togglePanel();
   }
 
+  public static final class StatusBarManager.DisableInfo {
+    method public boolean isRotationSuggestionDisabled();
+  }
+
   public final class SyncNotedAppOp implements android.os.Parcelable {
     ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
   }
@@ -424,11 +446,10 @@
 package android.app.admin {
 
   public class DevicePolicyManager {
-    method public int checkProvisioningPreCondition(@Nullable String, @NonNull String);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void clearOrganizationId();
     method @RequiresPermission(android.Manifest.permission.CLEAR_FREEZE_PERIOD) public void clearSystemUpdatePolicyFreezePeriodRecord();
-    method @Nullable public android.os.UserHandle createAndProvisionManagedProfile(@NonNull android.app.admin.ManagedProfileProvisioningParams) throws android.app.admin.ProvisioningException;
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceNetworkLogs();
-    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void forceRemoveActiveAdmin(@NonNull android.content.ComponentName, int);
     method @RequiresPermission(android.Manifest.permission.FORCE_DEVICE_POLICY_MANAGER_LOGS) public long forceSecurityLogs();
     method public void forceUpdateUserSetupComplete(int);
     method @NonNull public java.util.Set<java.lang.String> getDefaultCrossProfilePackages();
@@ -437,35 +458,20 @@
     method public long getLastNetworkLogRetrievalTime();
     method public long getLastSecurityLogRetrievalTime();
     method public java.util.List<java.lang.String> getOwnerInstalledCaCerts(@NonNull android.os.UserHandle);
-    method @NonNull @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public java.util.Set<java.lang.String> getPolicyExemptApps();
+    method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public java.util.Set<java.lang.String> getPolicyExemptApps();
     method public boolean isCurrentInputMethodSetByOwner();
     method public boolean isFactoryResetProtectionPolicySupported();
-    method @RequiresPermission(anyOf={"android.permission.MARK_DEVICE_ORGANIZATION_OWNED", "android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
+    method @RequiresPermission(anyOf={android.Manifest.permission.MARK_DEVICE_ORGANIZATION_OWNED, android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS}, conditional=true) public void markProfileOwnerOnOrganizationOwnedDevice(@NonNull android.content.ComponentName);
     method @NonNull public static String operationSafetyReasonToString(int);
     method @NonNull public static String operationToString(int);
-    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void provisionFullyManagedDevice(@NonNull android.app.admin.FullyManagedDeviceProvisioningParams) throws android.app.admin.ProvisioningException;
-    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public void resetDefaultCrossProfileIntentFilters(int);
-    method @RequiresPermission(allOf={"android.permission.MANAGE_DEVICE_ADMINS", android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
-    method @RequiresPermission("android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS") public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
-    method @RequiresPermission("android.permission.MANAGE_DEVICE_ADMINS") public void setNextOperationSafety(int, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public void resetDefaultCrossProfileIntentFilters(int);
+    method @RequiresPermission(allOf={android.Manifest.permission.MANAGE_DEVICE_ADMINS, android.Manifest.permission.INTERACT_ACROSS_USERS_FULL}) public void setActiveAdmin(@NonNull android.content.ComponentName, boolean, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwner(@NonNull android.content.ComponentName, @Nullable String, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public boolean setDeviceOwnerOnly(@NonNull android.content.ComponentName, @Nullable String, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_DEVICE_ADMINS) public void setNextOperationSafety(int, int);
     field public static final String ACTION_DATA_SHARING_RESTRICTION_APPLIED = "android.app.action.DATA_SHARING_RESTRICTION_APPLIED";
     field public static final String ACTION_DEVICE_POLICY_CONSTANTS_CHANGED = "android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED";
-    field public static final int CODE_ACCOUNTS_NOT_EMPTY = 6; // 0x6
-    field public static final int CODE_CANNOT_ADD_MANAGED_PROFILE = 11; // 0xb
-    field public static final int CODE_DEVICE_ADMIN_NOT_SUPPORTED = 13; // 0xd
-    field public static final int CODE_HAS_DEVICE_OWNER = 1; // 0x1
-    field public static final int CODE_HAS_PAIRED = 8; // 0x8
-    field public static final int CODE_MANAGED_USERS_NOT_SUPPORTED = 9; // 0x9
-    field public static final int CODE_NONSYSTEM_USER_EXISTS = 5; // 0x5
-    field public static final int CODE_NOT_SYSTEM_USER = 7; // 0x7
-    field @Deprecated public static final int CODE_NOT_SYSTEM_USER_SPLIT = 12; // 0xc
-    field public static final int CODE_OK = 0; // 0x0
-    field public static final int CODE_PROVISIONING_NOT_ALLOWED_FOR_NON_DEVELOPER_USERS = 15; // 0xf
     field @Deprecated public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14; // 0xe
-    field public static final int CODE_SYSTEM_USER = 10; // 0xa
-    field public static final int CODE_USER_HAS_PROFILE_OWNER = 2; // 0x2
-    field public static final int CODE_USER_NOT_RUNNING = 3; // 0x3
-    field public static final int CODE_USER_SETUP_COMPLETED = 4; // 0x4
     field public static final int OPERATION_CLEAR_APPLICATION_USER_DATA = 23; // 0x17
     field public static final int OPERATION_CREATE_AND_MANAGE_USER = 5; // 0x5
     field public static final int OPERATION_INSTALL_CA_CERT = 24; // 0x18
@@ -507,66 +513,6 @@
     field public static final int OPERATION_SWITCH_USER = 2; // 0x2
     field public static final int OPERATION_UNINSTALL_CA_CERT = 40; // 0x28
     field public static final int OPERATION_WIPE_DATA = 8; // 0x8
-    field public static final int PROVISIONING_RESULT_ADMIN_PACKAGE_INSTALLATION_FAILED = 3; // 0x3
-    field public static final int PROVISIONING_RESULT_PRE_CONDITION_FAILED = 1; // 0x1
-    field public static final int PROVISIONING_RESULT_PROFILE_CREATION_FAILED = 2; // 0x2
-    field public static final int PROVISIONING_RESULT_REMOVE_NON_REQUIRED_APPS_FAILED = 6; // 0x6
-    field public static final int PROVISIONING_RESULT_SETTING_PROFILE_OWNER_FAILED = 4; // 0x4
-    field public static final int PROVISIONING_RESULT_SET_DEVICE_OWNER_FAILED = 7; // 0x7
-    field public static final int PROVISIONING_RESULT_STARTING_PROFILE_FAILED = 5; // 0x5
-  }
-
-  public final class FullyManagedDeviceProvisioningParams implements android.os.Parcelable {
-    method public boolean canDeviceOwnerGrantSensorsPermissions();
-    method public int describeContents();
-    method @NonNull public android.content.ComponentName getDeviceAdminComponentName();
-    method public long getLocalTime();
-    method @Nullable public java.util.Locale getLocale();
-    method @NonNull public String getOwnerName();
-    method @Nullable public String getTimeZone();
-    method public boolean isLeaveAllSystemAppsEnabled();
-    method public void logParams(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FullyManagedDeviceProvisioningParams> CREATOR;
-  }
-
-  public static final class FullyManagedDeviceProvisioningParams.Builder {
-    ctor public FullyManagedDeviceProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams build();
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setDeviceOwnerCanGrantSensorsPermissions(boolean);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocalTime(long);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setLocale(@Nullable java.util.Locale);
-    method @NonNull public android.app.admin.FullyManagedDeviceProvisioningParams.Builder setTimeZone(@Nullable String);
-  }
-
-  public final class ManagedProfileProvisioningParams implements android.os.Parcelable {
-    method public int describeContents();
-    method @Nullable public android.accounts.Account getAccountToMigrate();
-    method @NonNull public String getOwnerName();
-    method @NonNull public android.content.ComponentName getProfileAdminComponentName();
-    method @Nullable public String getProfileName();
-    method public boolean isKeepAccountMigrated();
-    method public boolean isLeaveAllSystemAppsEnabled();
-    method public boolean isOrganizationOwnedProvisioning();
-    method public void logParams(@NonNull String);
-    method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.ManagedProfileProvisioningParams> CREATOR;
-  }
-
-  public static final class ManagedProfileProvisioningParams.Builder {
-    ctor public ManagedProfileProvisioningParams.Builder(@NonNull android.content.ComponentName, @NonNull String);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams build();
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setAccountToMigrate(@Nullable android.accounts.Account);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setKeepAccountMigrated(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setLeaveAllSystemAppsEnabled(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setOrganizationOwnedProvisioning(boolean);
-    method @NonNull public android.app.admin.ManagedProfileProvisioningParams.Builder setProfileName(@Nullable String);
-  }
-
-  public class ProvisioningException extends android.util.AndroidException {
-    ctor public ProvisioningException(@NonNull Exception, int);
-    method public int getProvisioningResult();
   }
 
   public static final class SecurityLog.SecurityEvent implements android.os.Parcelable {
@@ -609,6 +555,14 @@
 
 }
 
+package android.app.communal {
+
+  public final class CommunalManager {
+    method @RequiresPermission(android.Manifest.permission.WRITE_COMMUNAL_STATE) public void setCommunalViewShowing(boolean);
+  }
+
+}
+
 package android.app.contentsuggestions {
 
   public final class ContentSuggestionsManager {
@@ -677,6 +631,14 @@
 
 }
 
+package android.companion {
+
+  public abstract class CompanionDeviceService extends android.app.Service {
+    method public void onBindCompanionDeviceService(@NonNull android.content.Intent);
+  }
+
+}
+
 package android.content {
 
   public final class AttributionSource implements android.os.Parcelable {
@@ -741,6 +703,7 @@
     field public static final String FONT_SERVICE = "font";
     field public static final String POWER_EXEMPTION_SERVICE = "power_exemption";
     field @Deprecated public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
+    field @Deprecated public static final int RECEIVER_EXPORTED_UNAUDITED = 2; // 0x2
     field public static final String TEST_NETWORK_SERVICE = "test_network";
   }
 
@@ -780,6 +743,7 @@
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE = 1.7777778f;
     field public static final long OVERRIDE_MIN_ASPECT_RATIO_MEDIUM = 180326845L; // 0xabf91bdL
     field public static final float OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE = 1.5f;
+    field public static final long OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY = 203647190L; // 0xc2368d6L
     field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2
   }
 
@@ -809,7 +773,8 @@
     method @Nullable public String getDefaultTextClassifierPackageName();
     method @RequiresPermission(android.Manifest.permission.INJECT_EVENTS) public android.os.IBinder getHoldLockToken();
     method public abstract int getInstallReason(@NonNull String, @NonNull android.os.UserHandle);
-    method @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @Deprecated @NonNull public abstract java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
+    method @NonNull public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(@NonNull android.content.pm.PackageManager.ApplicationInfoFlags, int);
     method @Nullable public abstract String[] getNamesForUids(int[]);
     method @NonNull public String getPermissionControllerPackageName();
     method @NonNull public abstract String getServicesSystemSharedLibraryPackageName();
@@ -819,6 +784,7 @@
     method public void holdLock(android.os.IBinder, int);
     method @RequiresPermission(android.Manifest.permission.KEEP_UNINSTALLED_PACKAGES) public void setKeepUninstalledPackages(@NonNull java.util.List<java.lang.String>);
     field public static final String FEATURE_ADOPTABLE_STORAGE = "android.software.adoptable_storage";
+    field public static final String FEATURE_COMMUNAL_MODE = "android.software.communal_mode";
     field public static final String FEATURE_FILE_BASED_ENCRYPTION = "android.software.file_based_encryption";
     field public static final String FEATURE_HDMI_CEC = "android.hardware.hdmi.cec";
     field public static final int FLAG_PERMISSION_REVOKE_WHEN_REQUESTED = 128; // 0x80
@@ -1051,13 +1017,13 @@
 
   public final class SensorPrivacyManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacy(int, int, boolean);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_SENSOR_PRIVACY) public void setSensorPrivacyForProfileGroup(int, int, boolean);
   }
 
   public static class SensorPrivacyManager.Sources {
     field public static final int DIALOG = 3; // 0x3
     field public static final int OTHER = 5; // 0x5
     field public static final int QS_TILE = 1; // 0x1
+    field public static final int SAFETY_HUB = 6; // 0x6
     field public static final int SETTINGS = 2; // 0x2
     field public static final int SHELL = 4; // 0x4
   }
@@ -1133,10 +1099,10 @@
 package android.hardware.devicestate {
 
   public final class DeviceStateManager {
-    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
+    method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void cancelRequest(@NonNull android.hardware.devicestate.DeviceStateRequest);
     method @NonNull public int[] getSupportedStates();
     method public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
-    method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
+    method @RequiresPermission(value=android.Manifest.permission.CONTROL_DEVICE_STATE, conditional=true) public void requestState(@NonNull android.hardware.devicestate.DeviceStateRequest, @Nullable java.util.concurrent.Executor, @Nullable android.hardware.devicestate.DeviceStateRequest.Callback);
     method public void unregisterCallback(@NonNull android.hardware.devicestate.DeviceStateManager.DeviceStateCallback);
     field public static final int MAXIMUM_DEVICE_STATE = 255; // 0xff
     field public static final int MINIMUM_DEVICE_STATE = 0; // 0x0
@@ -1179,12 +1145,15 @@
 
   public final class DisplayManager {
     method public boolean areUserDisabledHdrTypesAllowed();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void clearUserPreferredDisplayMode();
     method @NonNull public int[] getUserDisabledHdrTypes();
+    method @Nullable public android.view.Display.Mode getUserPreferredDisplayMode();
     method public boolean isMinimalPostProcessingRequested(int);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setAreUserDisabledHdrTypesAllowed(boolean);
     method @RequiresPermission(android.Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE) public void setRefreshRateSwitchingType(int);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public void setShouldAlwaysRespectAppRequestedMode(boolean);
     method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void setUserDisabledHdrTypes(@NonNull int[]);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE) public void setUserPreferredDisplayMode(@NonNull android.view.Display.Mode);
     method @RequiresPermission(android.Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS) public boolean shouldAlwaysRespectAppRequestedMode();
     field public static final int SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS = 2; // 0x2
     field public static final int SWITCHING_TYPE_NONE = 0; // 0x0
@@ -1403,6 +1372,7 @@
 package android.media {
 
   public final class AudioAttributes implements android.os.Parcelable {
+    method public static int[] getSdkUsages();
     method @NonNull public static String usageToXsdString(int);
     method public static int xsdStringToUsage(@NonNull String);
   }
@@ -1425,12 +1395,20 @@
 
   public class AudioManager {
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioRecord getCallDownlinkExtractionAudioRecord(@NonNull android.media.AudioFormat);
+    method @NonNull @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public android.media.AudioTrack getCallUplinkInjectionAudioTrack(@NonNull android.media.AudioFormat);
     method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
     method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
+    method public static final int[] getPublicStreamTypes();
+    method @NonNull public java.util.List<java.lang.Integer> getReportedSurroundFormats();
+    method public int getStreamMinVolumeInt(int);
     method @NonNull public java.util.Map<java.lang.Integer,java.lang.Boolean> getSurroundFormats();
     method public boolean hasRegisteredDynamicPolicy();
     method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
+    method @RequiresPermission(android.Manifest.permission.CALL_AUDIO_INTERCEPTION) public boolean isPstnCallAudioInterceptable();
     method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
+    method public void setRampingRingerEnabled(boolean);
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setTestDeviceConnectionState(@NonNull android.media.AudioDeviceAttributes, boolean);
   }
 
   public static final class AudioRecord.MetricsConstants {
@@ -1451,9 +1429,13 @@
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public static float getMasterBalance();
     method public static final int getNumStreamTypes();
     method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public static int setMasterBalance(float);
+    method @NonNull public static String streamToString(int);
     field public static final int DEVICE_ROLE_DISABLED = 2; // 0x2
     field public static final int DEVICE_ROLE_NONE = 0; // 0x0
     field public static final int DEVICE_ROLE_PREFERRED = 1; // 0x1
+    field public static final int DIRECT_BITSTREAM_SUPPORTED = 4; // 0x4
+    field public static final int DIRECT_OFFLOAD_GAPLESS_SUPPORTED = 3; // 0x3
+    field public static final int DIRECT_OFFLOAD_SUPPORTED = 1; // 0x1
     field public static final int OFFLOAD_GAPLESS_SUPPORTED = 2; // 0x2
     field public static final int OFFLOAD_SUPPORTED = 1; // 0x1
     field public static final int STREAM_DEFAULT = -1; // 0xffffffff
@@ -1548,6 +1530,13 @@
     method @NonNull public android.media.audiopolicy.AudioPolicy.Builder setIsTestFocusPolicy(boolean);
   }
 
+  public final class AudioProductStrategy implements android.os.Parcelable {
+    method @NonNull public static android.media.AudioAttributes getDefaultAttributes();
+    method public int getLegacyStreamTypeForAudioAttributes(@NonNull android.media.AudioAttributes);
+    method public int getVolumeGroupIdForAudioAttributes(@NonNull android.media.AudioAttributes);
+    method public int getVolumeGroupIdForLegacyStreamType(int);
+  }
+
 }
 
 package android.media.metrics {
@@ -1787,7 +1776,7 @@
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createRestrictedProfile(@Nullable String);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo createUser(@Nullable String, @NonNull String, int);
     method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.Set<java.lang.String> getPreInstallableSystemPackages(@NonNull String);
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public String getUserType();
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
     method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
     method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
     method public static boolean isGuestUserEphemeral();
@@ -1799,10 +1788,6 @@
     method public int getAudioUsage();
   }
 
-  public static final class VibrationAttributes.Builder {
-    ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes, @Nullable android.os.VibrationEffect);
-  }
-
   public abstract class VibrationEffect implements android.os.Parcelable {
     method public static android.os.VibrationEffect get(int);
     method public static android.os.VibrationEffect get(int, boolean);
@@ -1819,13 +1804,9 @@
   }
 
   public static final class VibrationEffect.Composed extends android.os.VibrationEffect {
-    method @NonNull public android.os.VibrationEffect.Composed applyEffectStrength(int);
     method public long getDuration();
     method public int getRepeatIndex();
     method @NonNull public java.util.List<android.os.vibrator.VibrationEffectSegment> getSegments();
-    method @NonNull public android.os.VibrationEffect.Composed resolve(int);
-    method @NonNull public android.os.VibrationEffect.Composed scale(float);
-    method public void validate();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.Composed> CREATOR;
   }
@@ -1941,6 +1922,7 @@
   }
 
   public class StorageManager {
+    method public long computeStorageCacheBytes(@NonNull java.io.File);
     method @NonNull public static java.util.UUID convert(@NonNull String);
     method @NonNull public static String convert(@NonNull java.util.UUID);
     method public boolean isAppIoBlocked(@NonNull java.util.UUID, int, int, int);
@@ -1973,72 +1955,47 @@
 package android.os.vibrator {
 
   public final class PrebakedSegment extends android.os.vibrator.VibrationEffectSegment {
-    method @NonNull public android.os.vibrator.PrebakedSegment applyEffectStrength(int);
     method public int describeContents();
     method public long getDuration();
     method public int getEffectId();
     method public int getEffectStrength();
-    method public boolean hasNonZeroAmplitude();
-    method @NonNull public android.os.vibrator.PrebakedSegment resolve(int);
-    method @NonNull public android.os.vibrator.PrebakedSegment scale(float);
     method public boolean shouldFallback();
-    method public void validate();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrebakedSegment> CREATOR;
   }
 
   public final class PrimitiveSegment extends android.os.vibrator.VibrationEffectSegment {
-    method @NonNull public android.os.vibrator.PrimitiveSegment applyEffectStrength(int);
     method public int describeContents();
     method public int getDelay();
     method public long getDuration();
     method public int getPrimitiveId();
     method public float getScale();
-    method public boolean hasNonZeroAmplitude();
-    method @NonNull public android.os.vibrator.PrimitiveSegment resolve(int);
-    method @NonNull public android.os.vibrator.PrimitiveSegment scale(float);
-    method public void validate();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.PrimitiveSegment> CREATOR;
   }
 
   public final class RampSegment extends android.os.vibrator.VibrationEffectSegment {
-    method @NonNull public android.os.vibrator.RampSegment applyEffectStrength(int);
     method public int describeContents();
     method public long getDuration();
     method public float getEndAmplitude();
     method public float getEndFrequency();
     method public float getStartAmplitude();
     method public float getStartFrequency();
-    method public boolean hasNonZeroAmplitude();
-    method @NonNull public android.os.vibrator.RampSegment resolve(int);
-    method @NonNull public android.os.vibrator.RampSegment scale(float);
-    method public void validate();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.RampSegment> CREATOR;
   }
 
   public final class StepSegment extends android.os.vibrator.VibrationEffectSegment {
-    method @NonNull public android.os.vibrator.StepSegment applyEffectStrength(int);
     method public int describeContents();
     method public float getAmplitude();
     method public long getDuration();
     method public float getFrequency();
-    method public boolean hasNonZeroAmplitude();
-    method @NonNull public android.os.vibrator.StepSegment resolve(int);
-    method @NonNull public android.os.vibrator.StepSegment scale(float);
-    method public void validate();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.StepSegment> CREATOR;
   }
 
   public abstract class VibrationEffectSegment implements android.os.Parcelable {
-    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T applyEffectStrength(int);
     method public abstract long getDuration();
-    method public abstract boolean hasNonZeroAmplitude();
-    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T resolve(int);
-    method @NonNull public abstract <T extends android.os.vibrator.VibrationEffectSegment> T scale(float);
-    method public abstract void validate();
     field @NonNull public static final android.os.Parcelable.Creator<android.os.vibrator.VibrationEffectSegment> CREATOR;
   }
 
@@ -2112,7 +2069,7 @@
   }
 
   public static final class ContactsContract.CommonDataKinds.Phone implements android.provider.ContactsContract.CommonDataKinds.CommonColumns android.provider.ContactsContract.DataColumnsWithJoins {
-    field public static final android.net.Uri ENTERPRISE_CONTENT_URI;
+    field @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public static final android.net.Uri ENTERPRISE_CONTENT_URI;
   }
 
   public static final class ContactsContract.PinnedPositions {
@@ -2130,6 +2087,7 @@
     field public static final String NAMESPACE_CONSTRAIN_DISPLAY_APIS = "constrain_display_apis";
     field public static final String NAMESPACE_DEVICE_IDLE = "device_idle";
     field public static final String NAMESPACE_JOB_SCHEDULER = "jobscheduler";
+    field public static final String NAMESPACE_SELECTION_TOOLBAR = "selection_toolbar";
   }
 
   public final class Settings {
@@ -2155,7 +2113,10 @@
     field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
     field public static final String SHOW_FIRST_CRASH_DIALOG = "show_first_crash_dialog";
     field public static final String USER_DISABLED_HDR_FORMATS = "user_disabled_hdr_formats";
-    field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
+    field public static final String USER_PREFERRED_REFRESH_RATE = "user_preferred_refresh_rate";
+    field public static final String USER_PREFERRED_RESOLUTION_HEIGHT = "user_preferred_resolution_height";
+    field public static final String USER_PREFERRED_RESOLUTION_WIDTH = "user_preferred_resolution_width";
+    field @Deprecated public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
   }
 
   public static final class Settings.Secure extends android.provider.Settings.NameValueTable {
@@ -2201,8 +2162,8 @@
   }
 
   public class KeyStoreException extends java.lang.Exception {
-    ctor public KeyStoreException(int, String);
     method public int getErrorCode();
+    method public static boolean hasFailureInfoForError(int);
   }
 
 }
@@ -2337,6 +2298,17 @@
 
 }
 
+package android.service.dreams {
+
+  public abstract class DreamOverlayService extends android.app.Service {
+    ctor public DreamOverlayService();
+    method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent);
+    method public abstract void onStartDream(@NonNull android.view.WindowManager.LayoutParams);
+    method public final void requestExit();
+  }
+
+}
+
 package android.service.notification {
 
   @Deprecated public abstract class ConditionProviderService extends android.app.Service {
@@ -2389,6 +2361,10 @@
     method @RequiresPermission(allOf={android.Manifest.permission.RECORD_AUDIO, android.Manifest.permission.CAPTURE_AUDIO_HOTWORD}) public void triggerHardwareRecognitionEventForTest(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[]);
   }
 
+  public final class VisibleActivityInfo implements android.os.Parcelable {
+    ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder);
+  }
+
 }
 
 package android.service.watchdog {
@@ -2722,6 +2698,7 @@
   }
 
   public final class Display {
+    method @NonNull public android.view.Display.Mode getDefaultMode();
     method @NonNull public int[] getReportedHdrTypes();
     method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
     method public int getType();
@@ -2735,6 +2712,11 @@
     field public static final int TYPE_WIFI = 3; // 0x3
   }
 
+  public static final class Display.Mode implements android.os.Parcelable {
+    ctor public Display.Mode(int, int, float);
+    method public boolean matches(int, int, float);
+  }
+
   public class FocusFinder {
     method public static void sort(android.view.View[], int, int, android.view.ViewGroup, boolean);
   }
@@ -2748,7 +2730,7 @@
     method public static String actionToString(int);
     method public final void setDisplayId(int);
     field public static final int FLAG_IS_ACCESSIBILITY_EVENT = 2048; // 0x800
-    field public static final int LAST_KEYCODE = 288; // 0x120
+    field public static final int LAST_KEYCODE = 304; // 0x130
   }
 
   public final class KeyboardShortcutGroup implements android.os.Parcelable {
@@ -2774,11 +2756,9 @@
 
   public final class SurfaceControl implements android.os.Parcelable {
     ctor public SurfaceControl(@NonNull android.view.SurfaceControl, @NonNull String);
-    method public static long acquireFrameRateFlexibilityToken();
     method @NonNull public static android.os.IBinder getInternalDisplayToken();
     method public boolean isSameSurface(@NonNull android.view.SurfaceControl);
     method public static void overrideHdrTypes(@NonNull android.os.IBinder, @NonNull int[]);
-    method public static void releaseFrameRateFlexibilityToken(long);
   }
 
   public class SurfaceControlViewHost {
@@ -2840,6 +2820,7 @@
     method @Nullable public final android.os.IBinder getWindowContextToken();
     method public final void setWindowContextToken(@NonNull android.os.IBinder);
     field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
+    field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
     field public static final int PRIVATE_FLAG_NO_MOVE_ANIMATION = 64; // 0x40
     field public CharSequence accessibilityTitle;
     field public int privateFlags;
@@ -2850,20 +2831,13 @@
 package android.view.accessibility {
 
   public final class AccessibilityManager {
-    method public void addAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener, @Nullable android.os.Handler);
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public java.util.List<java.lang.String> getAccessibilityShortcutTargets(int);
-    method public void removeAccessibilityServicesStateChangeListener(@NonNull android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener);
-  }
-
-  public static interface AccessibilityManager.AccessibilityServicesStateChangeListener {
-    method public void onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager);
   }
 
   public class AccessibilityNodeInfo implements android.os.Parcelable {
     method public void addChild(@NonNull android.os.IBinder);
     method public long getSourceNodeId();
     method public void setLeashedParent(@Nullable android.os.IBinder, int);
-    method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
     method public void writeToParcelNoRecycle(android.os.Parcel, int);
   }
 
@@ -2871,6 +2845,10 @@
     method public long getAccessibilityIdForRegion(@NonNull android.graphics.Region);
   }
 
+  public class AccessibilityRecord {
+    method public void setDisplayId(int);
+  }
+
   public final class AccessibilityWindowInfo implements android.os.Parcelable {
     method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
   }
@@ -2899,6 +2877,7 @@
   }
 
   public final class AutofillManager {
+    field public static final String DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "compat_mode_allowed_packages";
     field public static final String DEVICE_CONFIG_AUTOFILL_SMART_SUGGESTION_SUPPORTED_MODES = "smart_suggestion_supported_modes";
     field public static final int FLAG_SMART_SUGGESTION_OFF = 0; // 0x0
     field public static final int FLAG_SMART_SUGGESTION_SYSTEM = 1; // 0x1
@@ -3182,14 +3161,6 @@
 
   public final class SplashScreenView extends android.widget.FrameLayout {
     method @Nullable public android.view.View getBrandingView();
-    method @ColorInt public int getIconBackgroundColor();
-  }
-
-  public final class StartingWindowInfo implements android.os.Parcelable {
-    ctor public StartingWindowInfo();
-    method public int describeContents();
-    method public void writeToParcel(@NonNull android.os.Parcel, int);
-    field @NonNull public static final android.os.Parcelable.Creator<android.window.StartingWindowInfo> CREATOR;
   }
 
   public final class TaskAppearedInfo implements android.os.Parcelable {
@@ -3201,9 +3172,57 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskAppearedInfo> CREATOR;
   }
 
+  public final class TaskFragmentCreationParams implements android.os.Parcelable {
+    method @NonNull public android.os.IBinder getFragmentToken();
+    method @NonNull public android.graphics.Rect getInitialBounds();
+    method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizer();
+    method @NonNull public android.os.IBinder getOwnerToken();
+    method public int getWindowingMode();
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentCreationParams> CREATOR;
+  }
+
+  public static final class TaskFragmentCreationParams.Builder {
+    ctor public TaskFragmentCreationParams.Builder(@NonNull android.window.TaskFragmentOrganizerToken, @NonNull android.os.IBinder, @NonNull android.os.IBinder);
+    method @NonNull public android.window.TaskFragmentCreationParams build();
+    method @NonNull public android.window.TaskFragmentCreationParams.Builder setInitialBounds(@NonNull android.graphics.Rect);
+    method @NonNull public android.window.TaskFragmentCreationParams.Builder setWindowingMode(int);
+  }
+
+  public final class TaskFragmentInfo implements android.os.Parcelable {
+    method public boolean equalsForTaskFragmentOrganizer(@Nullable android.window.TaskFragmentInfo);
+    method @NonNull public java.util.List<android.os.IBinder> getActivities();
+    method @NonNull public android.content.res.Configuration getConfiguration();
+    method @NonNull public android.os.IBinder getFragmentToken();
+    method @NonNull public android.graphics.Point getPositionInParent();
+    method public int getRunningActivityCount();
+    method @NonNull public android.window.WindowContainerToken getToken();
+    method public int getWindowingMode();
+    method public boolean hasRunningActivity();
+    method public boolean isEmpty();
+    method public boolean isTaskClearedForReuse();
+    method public boolean isVisible();
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentInfo> CREATOR;
+  }
+
+  public class TaskFragmentOrganizer extends android.window.WindowOrganizer {
+    ctor public TaskFragmentOrganizer(@NonNull java.util.concurrent.Executor);
+    method @NonNull public java.util.concurrent.Executor getExecutor();
+    method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
+    method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
+    method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+    method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
+    method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
+    method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
+    method @CallSuper public void registerOrganizer();
+    method @CallSuper public void unregisterOrganizer();
+  }
+
+  public final class TaskFragmentOrganizerToken implements android.os.Parcelable {
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.TaskFragmentOrganizerToken> CREATOR;
+  }
+
   public class TaskOrganizer extends android.window.WindowOrganizer {
     ctor public TaskOrganizer();
-    method @BinderThread public void addStartingWindow(@NonNull android.window.StartingWindowInfo, @NonNull android.os.IBinder);
     method @BinderThread public void copySplashScreenView(int);
     method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void createRootTask(int, int, @Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
@@ -3216,7 +3235,6 @@
     method @BinderThread public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @BinderThread public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
     method @CallSuper @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public java.util.List<android.window.TaskAppearedInfo> registerOrganizer();
-    method @BinderThread public void removeStartingWindow(int, @Nullable android.view.SurfaceControl, @Nullable android.graphics.Rect, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void setInterceptBackPressedOnTaskRoot(@NonNull android.window.WindowContainerToken, boolean);
     method @CallSuper @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void unregisterOrganizer();
   }
@@ -3229,26 +3247,42 @@
 
   public final class WindowContainerTransaction implements android.os.Parcelable {
     ctor public WindowContainerTransaction();
+    method @NonNull public android.window.WindowContainerTransaction createTaskFragment(@NonNull android.window.TaskFragmentCreationParams);
+    method @NonNull public android.window.WindowContainerTransaction deleteTaskFragment(@NonNull android.window.WindowContainerToken);
     method public int describeContents();
     method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction reparentActivityToTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder);
+    method @NonNull public android.window.WindowContainerTransaction reparentChildren(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken);
     method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
     method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
-    method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+    method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
     method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
     method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
+    method @NonNull public android.window.WindowContainerTransaction setErrorCallbackToken(@NonNull android.os.IBinder);
     method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
     method @NonNull public android.window.WindowContainerTransaction setLaunchRoot(@NonNull android.window.WindowContainerToken, @Nullable int[], @Nullable int[]);
     method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
     method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
     method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
+    method @NonNull public android.window.WindowContainerTransaction startActivityInTaskFragment(@NonNull android.os.IBinder, @NonNull android.os.IBinder, @NonNull android.content.Intent, @Nullable android.os.Bundle);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR;
   }
 
+  public static class WindowContainerTransaction.TaskFragmentAdjacentParams {
+    ctor public WindowContainerTransaction.TaskFragmentAdjacentParams();
+    ctor public WindowContainerTransaction.TaskFragmentAdjacentParams(@NonNull android.os.Bundle);
+    method public void setShouldDelayPrimaryLastActivityRemoval(boolean);
+    method public void setShouldDelaySecondaryLastActivityRemoval(boolean);
+    method public boolean shouldDelayPrimaryLastActivityRemoval();
+    method public boolean shouldDelaySecondaryLastActivityRemoval();
+  }
+
   public abstract class WindowContainerTransactionCallback {
     ctor public WindowContainerTransactionCallback();
     method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
@@ -3256,15 +3290,15 @@
 
   public class WindowOrganizer {
     ctor public WindowOrganizer();
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
-    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
+    method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
+    method @RequiresPermission(value=android.Manifest.permission.MANAGE_ACTIVITY_TASKS, conditional=true) public void applyTransaction(@NonNull android.window.WindowContainerTransaction);
   }
 
   @UiContext public abstract class WindowProviderService extends android.app.Service {
     ctor public WindowProviderService();
     method public final void attachToWindowToken(@NonNull android.os.IBinder);
     method @NonNull public int getInitialDisplayId();
-    method @Nullable public android.os.Bundle getWindowContextOptions();
+    method @CallSuper @Nullable public android.os.Bundle getWindowContextOptions();
     method public abstract int getWindowType();
   }
 
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
index c5d9c2f..3dd2893 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/apis/wifi-current.txt
@@ -25,6 +25,8 @@
     method public static int convertFrequencyMhzToChannelIfSupported(int);
     method public int describeContents();
     method @NonNull public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
+    method @NonNull public int[] getSecurityTypes();
+    method @Nullable public android.net.wifi.WifiSsid getWifiSsid();
     method public int getWifiStandard();
     method public boolean is80211mcResponder();
     method public boolean isPasspointNetwork();
@@ -36,7 +38,11 @@
     field public static final int CHANNEL_WIDTH_80MHZ = 2; // 0x2
     field public static final int CHANNEL_WIDTH_80MHZ_PLUS_MHZ = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.ScanResult> CREATOR;
-    field public String SSID;
+    field public static final int PREAMBLE_HE = 3; // 0x3
+    field public static final int PREAMBLE_HT = 1; // 0x1
+    field public static final int PREAMBLE_LEGACY = 0; // 0x0
+    field public static final int PREAMBLE_VHT = 2; // 0x2
+    field @Deprecated public String SSID;
     field public static final int UNSPECIFIED = -1; // 0xffffffff
     field public static final int WIFI_BAND_24_GHZ = 1; // 0x1
     field public static final int WIFI_BAND_5_GHZ = 2; // 0x2
@@ -74,7 +80,8 @@
     method @Nullable public android.net.MacAddress getBssid();
     method @Nullable public String getPassphrase();
     method public int getSecurityType();
-    method @Nullable public String getSsid();
+    method @Deprecated @Nullable public String getSsid();
+    method @Nullable public android.net.wifi.WifiSsid getWifiSsid();
     method public boolean isHiddenSsid();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
@@ -109,13 +116,19 @@
     method public int describeContents();
     method @Deprecated public android.net.ProxyInfo getHttpProxy();
     method @Deprecated @NonNull public String getKey();
+    method @Deprecated public int getMacRandomizationSetting();
     method @Deprecated @NonNull public android.net.MacAddress getRandomizedMacAddress();
     method @Deprecated public boolean isPasspoint();
     method @Deprecated public void setHttpProxy(android.net.ProxyInfo);
+    method @Deprecated public void setMacRandomizationSetting(int);
     method @Deprecated public void setSecurityParams(int);
     method public void writeToParcel(android.os.Parcel, int);
     field @Deprecated public String BSSID;
     field @Deprecated public String FQDN;
+    field @Deprecated public static final int RANDOMIZATION_AUTO = 3; // 0x3
+    field @Deprecated public static final int RANDOMIZATION_NONE = 0; // 0x0
+    field @Deprecated public static final int RANDOMIZATION_NON_PERSISTENT = 2; // 0x2
+    field @Deprecated public static final int RANDOMIZATION_PERSISTENT = 1; // 0x1
     field @Deprecated public static final int SECURITY_TYPE_EAP = 3; // 0x3
     field @Deprecated public static final int SECURITY_TYPE_EAP_SUITE_B = 5; // 0x5
     field @Deprecated public static final int SECURITY_TYPE_EAP_WPA3_ENTERPRISE = 9; // 0x9
@@ -297,7 +310,7 @@
     method @Nullable public java.util.List<android.net.wifi.ScanResult.InformationElement> getInformationElements();
     method @Deprecated public int getIpAddress();
     method public int getLinkSpeed();
-    method @RequiresPermission(allOf={android.Manifest.permission.LOCAL_MAC_ADDRESS, android.Manifest.permission.ACCESS_FINE_LOCATION}) public String getMacAddress();
+    method public String getMacAddress();
     method public int getMaxSupportedRxLinkSpeedMbps();
     method public int getMaxSupportedTxLinkSpeedMbps();
     method public int getNetworkId();
@@ -310,6 +323,7 @@
     method public android.net.wifi.SupplicantState getSupplicantState();
     method @IntRange(from=0xffffffff) public int getTxLinkSpeedMbps();
     method public int getWifiStandard();
+    method public boolean isRestricted();
     method @NonNull public android.net.wifi.WifiInfo makeCopy(long);
     method public void writeToParcel(android.os.Parcel, int);
     field public static final String FREQUENCY_UNITS = "MHz";
@@ -343,7 +357,7 @@
 
   public class WifiManager {
     method @Deprecated public int addNetwork(android.net.wifi.WifiConfiguration);
-    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration);
+    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING}, conditional=true) public android.net.wifi.WifiManager.AddNetworkResult addNetworkPrivileged(@NonNull android.net.wifi.WifiConfiguration);
     method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int addNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
     method public void addOrUpdatePasspointConfiguration(android.net.wifi.hotspot2.PasspointConfiguration);
     method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public void addSuggestionConnectionStatusListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
@@ -358,16 +372,16 @@
     method @Deprecated public boolean disableNetwork(int);
     method @Deprecated public boolean disconnect();
     method @Deprecated public boolean enableNetwork(int, boolean);
-    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void flushPasspointAnqpCache();
+    method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_MANAGED_PROVISIONING, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}, conditional=true) public void flushPasspointAnqpCache();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiConfiguration> getCallerConfiguredNetworks();
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.ACCESS_WIFI_STATE}) public java.util.List<android.net.wifi.WifiConfiguration> getConfiguredNetworks();
-    method @Deprecated public android.net.wifi.WifiInfo getConnectionInfo();
+    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public android.net.wifi.WifiInfo getConnectionInfo();
     method @Deprecated public android.net.DhcpInfo getDhcpInfo();
     method public int getMaxNumberOfNetworkSuggestionsPerApp();
     method @IntRange(from=0) public int getMaxSignalLevel();
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public java.util.List<android.net.wifi.WifiNetworkSuggestion> getNetworkSuggestions();
-    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
-    method public java.util.List<android.net.wifi.ScanResult> getScanResults();
+    method @Deprecated public java.util.List<android.net.wifi.hotspot2.PasspointConfiguration> getPasspointConfigurations();
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public java.util.List<android.net.wifi.ScanResult> getScanResults();
     method public int getWifiState();
     method public boolean is24GHzBandSupported();
     method public boolean is5GHzBandSupported();
@@ -395,6 +409,7 @@
     method public boolean isWapiSupported();
     method public boolean isWifiDisplayR2Supported();
     method public boolean isWifiEnabled();
+    method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public boolean isWifiPasspointEnabled();
     method public boolean isWifiStandardSupported(int);
     method public boolean isWpa3SaeH2eSupported();
     method public boolean isWpa3SaePublicKeySupported();
@@ -406,22 +421,25 @@
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerScanResultsCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.ScanResultsCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void registerSubsystemRestartTrackingCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
     method @Deprecated public boolean removeNetwork(int);
-    method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
+    method @Deprecated @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>);
+    method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public int removeNetworkSuggestions(@NonNull java.util.List<android.net.wifi.WifiNetworkSuggestion>, int);
     method @RequiresPermission(android.Manifest.permission.CHANGE_WIFI_STATE) public boolean removeNonCallerConfiguredNetworks();
-    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_CARRIER_PROVISIONING}) public void removePasspointConfiguration(String);
+    method @Deprecated public void removePasspointConfiguration(String);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionConnectionStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionConnectionStatusListener);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void removeSuggestionUserApprovalStatusListener(@NonNull android.net.wifi.WifiManager.SuggestionUserApprovalStatusListener);
     method @Deprecated public boolean saveConfiguration();
     method public void setTdlsEnabled(java.net.InetAddress, boolean);
     method public void setTdlsEnabledWithMacAddress(String, boolean);
     method @Deprecated public boolean setWifiEnabled(boolean);
-    method @RequiresPermission(allOf={android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, @Nullable android.os.Handler);
     method @Deprecated public boolean startScan();
     method @Deprecated public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterScanResultsCallback(@NonNull android.net.wifi.WifiManager.ScanResultsCallback);
     method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void unregisterSubsystemRestartTrackingCallback(@NonNull android.net.wifi.WifiManager.SubsystemRestartTrackingCallback);
     method @Deprecated public int updateNetwork(android.net.wifi.WifiConfiguration);
     field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
+    field public static final int ACTION_REMOVE_SUGGESTION_DISCONNECT = 2; // 0x2
+    field public static final int ACTION_REMOVE_SUGGESTION_LINGER = 1; // 0x1
     field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
     field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
     field public static final String ACTION_WIFI_SCAN_AVAILABILITY_CHANGED = "android.net.wifi.action.WIFI_SCAN_AVAILABILITY_CHANGED";
@@ -586,11 +604,13 @@
     method public int describeContents();
     method @Nullable public android.net.MacAddress getBssid();
     method @Nullable public android.net.wifi.WifiEnterpriseConfig getEnterpriseConfig();
+    method public int getMacRandomizationSetting();
     method @Nullable public String getPassphrase();
     method @Nullable public android.net.wifi.hotspot2.PasspointConfiguration getPasspointConfig();
     method @IntRange(from=0) public int getPriority();
     method @IntRange(from=0) public int getPriorityGroup();
     method @Nullable public String getSsid();
+    method @Nullable public android.os.ParcelUuid getSubscriptionGroup();
     method public int getSubscriptionId();
     method public boolean isAppInteractionRequired();
     method public boolean isCarrierMerged();
@@ -599,6 +619,7 @@
     method public boolean isHiddenSsid();
     method public boolean isInitialAutojoinEnabled();
     method public boolean isMetered();
+    method public boolean isRestricted();
     method public boolean isUntrusted();
     method public boolean isUserInteractionRequired();
     method public void writeToParcel(android.os.Parcel, int);
@@ -624,7 +645,9 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriorityGroup(@IntRange(from=0) int);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setRestricted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSsid(@NonNull String);
+    method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionGroup(@NonNull android.os.ParcelUuid);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setSubscriptionId(int);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setUntrusted(boolean);
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWapiEnterpriseConfig(@NonNull android.net.wifi.WifiEnterpriseConfig);
@@ -637,6 +660,17 @@
     method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setWpa3Passphrase(@NonNull String);
   }
 
+  public final class WifiSsid implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public static android.net.wifi.WifiSsid fromBytes(@Nullable byte[]);
+    method @NonNull public static android.net.wifi.WifiSsid fromString(@Nullable String);
+    method @NonNull public static android.net.wifi.WifiSsid fromUtf8Text(@Nullable CharSequence);
+    method @NonNull public byte[] getBytes();
+    method @Nullable public CharSequence getUtf8Text();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiSsid> CREATOR;
+  }
+
   public class WpsInfo implements android.os.Parcelable {
     ctor public WpsInfo();
     ctor public WpsInfo(android.net.wifi.WpsInfo);
@@ -661,6 +695,7 @@
     ctor public AttachCallback();
     method public void onAttachFailed();
     method public void onAttached(android.net.wifi.aware.WifiAwareSession);
+    method public void onShutDown();
   }
 
   public final class AwareResources implements android.os.Parcelable {
@@ -678,6 +713,10 @@
     method public int getMaxMatchFilterLength();
     method public int getMaxServiceNameLength();
     method public int getMaxServiceSpecificInfoLength();
+    method @IntRange(from=1) public int getNumberOfSupportedDataInterfaces();
+    method @IntRange(from=1) public int getNumberOfSupportedDataPaths();
+    method @IntRange(from=1) public int getNumberOfSupportedPublishSessions();
+    method @IntRange(from=1) public int getNumberOfSupportedSubscribeSessions();
     method public int getSupportedCipherSuites();
     method public boolean isInstantCommunicationModeSupported();
     method public void writeToParcel(android.os.Parcel, int);
@@ -773,8 +812,8 @@
   }
 
   public class WifiAwareManager {
-    method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @Nullable android.os.Handler);
-    method public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE}) public void attach(@NonNull android.net.wifi.aware.AttachCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void attach(@NonNull android.net.wifi.aware.AttachCallback, @NonNull android.net.wifi.aware.IdentityChangedListener, @Nullable android.os.Handler);
     method @Nullable public android.net.wifi.aware.AwareResources getAvailableAwareResources();
     method @Nullable public android.net.wifi.aware.Characteristics getCharacteristics();
     method public boolean isAvailable();
@@ -816,8 +855,8 @@
     method public void close();
     method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierOpen(int, @NonNull byte[]);
     method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPassphrase(int, @NonNull byte[], @NonNull String);
-    method public void publish(@NonNull android.net.wifi.aware.PublishConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
-    method public void subscribe(@NonNull android.net.wifi.aware.SubscribeConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void publish(@NonNull android.net.wifi.aware.PublishConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.NEARBY_WIFI_DEVICES}, conditional=true) public void subscribe(@NonNull android.net.wifi.aware.SubscribeConfig, @NonNull android.net.wifi.aware.DiscoverySessionCallback, @Nullable android.os.Handler);
   }
 
 }
@@ -1040,30 +1079,32 @@
   }
 
   public class WifiP2pManager {
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void addLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void addServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void cancelConnect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearLocalServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void clearServiceRequests(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void connect(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pConfig, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void createGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void createGroup(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pConfig, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void discoverPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void discoverServices(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public android.net.wifi.p2p.WifiP2pManager.Channel initialize(android.content.Context, android.os.Looper, android.net.wifi.p2p.WifiP2pManager.ChannelListener);
     method public void removeGroup(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeLocalService(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceInfo, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void removeServiceRequest(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.nsd.WifiP2pServiceRequest, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void requestConnectionInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestDeviceInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestDeviceInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DeviceInfoListener);
     method public void requestDiscoveryState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.DiscoveryStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestGroupInfo(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.GroupInfoListener);
     method public void requestNetworkInfo(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.NetworkInfoListener);
     method public void requestP2pState(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @NonNull android.net.wifi.p2p.WifiP2pManager.P2pStateListener);
-    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void requestPeers(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.PeerListListener);
     method public void setDnsSdResponseListeners(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.DnsSdServiceResponseListener, android.net.wifi.p2p.WifiP2pManager.DnsSdTxtRecordListener);
     method public void setServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ServiceResponseListener);
     method public void setUpnpServiceResponseListener(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.UpnpServiceResponseListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.NEARBY_WIFI_DEVICES, android.Manifest.permission.ACCESS_FINE_LOCATION}, conditional=true) public void startListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
+    method public void stopListening(@NonNull android.net.wifi.p2p.WifiP2pManager.Channel, @Nullable android.net.wifi.p2p.WifiP2pManager.ActionListener);
     method public void stopPeerDiscovery(android.net.wifi.p2p.WifiP2pManager.Channel, android.net.wifi.p2p.WifiP2pManager.ActionListener);
     field public static final int BUSY = 2; // 0x2
     field public static final int ERROR = 0; // 0x0
@@ -1282,10 +1323,12 @@
 
   public static final class RangingRequest.Builder {
     ctor public RangingRequest.Builder();
-    method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
-    method public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addAccessPoint(@NonNull android.net.wifi.ScanResult);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
     method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoint(@NonNull android.net.wifi.ScanResult);
     method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addNon80211mcCapableAccessPoints(@NonNull java.util.List<android.net.wifi.ScanResult>);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addResponder(@NonNull android.net.wifi.rtt.ResponderConfig);
+    method @NonNull public android.net.wifi.rtt.RangingRequest.Builder addResponders(@NonNull java.util.List<android.net.wifi.rtt.ResponderConfig>);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.MacAddress);
     method public android.net.wifi.rtt.RangingRequest.Builder addWifiAwarePeer(@NonNull android.net.wifi.aware.PeerHandle);
     method public android.net.wifi.rtt.RangingRequest build();
@@ -1320,6 +1363,32 @@
     field public static final int STATUS_CODE_FAIL_RTT_NOT_AVAILABLE = 2; // 0x2
   }
 
+  public final class ResponderConfig implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public static android.net.wifi.rtt.ResponderConfig fromScanResult(@NonNull android.net.wifi.ScanResult);
+    method public int getCenterFreq0Mhz();
+    method public int getCenterFreq1Mhz();
+    method public int getChannelWidth();
+    method public int getFrequencyMhz();
+    method @Nullable public android.net.MacAddress getMacAddress();
+    method public int getPreamble();
+    method public boolean is80211mcSupported();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.rtt.ResponderConfig> CREATOR;
+  }
+
+  public static final class ResponderConfig.Builder {
+    ctor public ResponderConfig.Builder();
+    method @NonNull public android.net.wifi.rtt.ResponderConfig build();
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder set80211mcSupported(boolean);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setCenterFreq0Mhz(int);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setCenterFreq1Mhz(int);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setChannelWidth(int);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setFrequencyMhz(int);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setMacAddress(@NonNull android.net.MacAddress);
+    method @NonNull public android.net.wifi.rtt.ResponderConfig.Builder setPreamble(int);
+  }
+
   public final class ResponderLocation implements android.os.Parcelable {
     method public int describeContents();
     method public double getAltitude();
@@ -1362,7 +1431,7 @@
 
   public class WifiRttManager {
     method public boolean isAvailable();
-    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE}) public void startRanging(@NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
+    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_WIFI_STATE, android.Manifest.permission.NEARBY_WIFI_DEVICES}) public void startRanging(@NonNull android.net.wifi.rtt.RangingRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.rtt.RangingResultCallback);
     field public static final String ACTION_WIFI_RTT_STATE_CHANGED = "android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED";
   }
 
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
new file mode 100644
index 0000000..4984775
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableAccountManagerCallback.java.txt
@@ -0,0 +1,79 @@
+/*
+ * 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.remoteframeworkclasses;
+
+import android.accounts.AccountManagerCallback;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link AccountManagerCallback} for clients
+ * which do not need to actually use the {@link AccountManagerCallback} param or return value.
+ */
+@CustomParcelableWrapper(originalType = AccountManagerCallback.class)
+public final class NullParcelableAccountManagerCallback<F> implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link AccountManagerCallback}.
+     */
+    public static <F> NullParcelableAccountManagerCallback of(
+            Bundler bundler, BundlerType type,
+            AccountManagerCallback<F> accountManagerCallback) {
+
+        if (accountManagerCallback != null) {
+            throw new IllegalArgumentException("accountManagerCallback can only be null");
+        }
+
+        return new NullParcelableAccountManagerCallback<F>();
+    }
+
+    private NullParcelableAccountManagerCallback() {
+    }
+
+    public AccountManagerCallback<F> get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableAccountManagerCallback> CREATOR =
+            new Creator<NullParcelableAccountManagerCallback>() {
+                @Override
+                public NullParcelableAccountManagerCallback createFromParcel(Parcel in) {
+                    return new NullParcelableAccountManagerCallback();
+                }
+
+                @Override
+                public NullParcelableAccountManagerCallback[] newArray(int size) {
+                    return new NullParcelableAccountManagerCallback[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
new file mode 100644
index 0000000..6000472
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableActivity.java.txt
@@ -0,0 +1,79 @@
+/*
+ * 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.remoteframeworkclasses;
+
+import android.app.Activity;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Activity} for clients
+ * which do not need to actually use the {@link Activity} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Activity.class)
+public final class NullParcelableActivity implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link Activity}.
+     */
+    public static <F> NullParcelableActivity of(
+            Bundler bundler, BundlerType type,
+            Activity activity) {
+
+       if (activity != null) {
+           throw new IllegalArgumentException("activity can only be null");
+       }
+
+        return new NullParcelableActivity();
+    }
+
+    private NullParcelableActivity() {
+    }
+
+    public Activity get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableActivity> CREATOR =
+            new Creator<NullParcelableActivity>() {
+                @Override
+                public NullParcelableActivity createFromParcel(Parcel in) {
+                    return new NullParcelableActivity();
+                }
+
+                @Override
+                public NullParcelableActivity[] newArray(int size) {
+                    return new NullParcelableActivity[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
new file mode 100644
index 0000000..92692ad
--- /dev/null
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableHandler.java.txt
@@ -0,0 +1,79 @@
+/*
+ * 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.remoteframeworkclasses;
+
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.google.android.enterprise.connectedapps.annotations.CustomParcelableWrapper;
+import com.google.android.enterprise.connectedapps.internal.Bundler;
+import com.google.android.enterprise.connectedapps.internal.BundlerType;
+
+/**
+ * This parcelable wrapper just passes null to callers.
+ *
+ * <p>It is not functional and only enables use of {@link Handler} for clients
+ * which do not need to actually use the {@link Handler} param or return value.
+ */
+@CustomParcelableWrapper(originalType = Handler.class)
+public final class NullParcelableHandler implements Parcelable {
+
+    /**
+     * Create a wrapper for a given {@link Handler}.
+     */
+    public static <F> NullParcelableHandler of(
+            Bundler bundler, BundlerType type,
+            Handler handler) {
+
+        if (handler != null) {
+            throw new IllegalArgumentException("handler can only be null");
+        }
+
+        return new NullParcelableHandler();
+    }
+
+    private NullParcelableHandler() {
+    }
+
+    public Handler get() {
+        return null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @SuppressWarnings("rawtypes")
+    public static final Creator<NullParcelableHandler> CREATOR =
+            new Creator<NullParcelableHandler>() {
+                @Override
+                public NullParcelableHandler createFromParcel(Parcel in) {
+                    return new NullParcelableHandler();
+                }
+
+                @Override
+                public NullParcelableHandler[] newArray(int size) {
+                    return new NullParcelableHandler[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
index 22217a3..7225c75 100644
--- a/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
+++ b/common/device-side/bedstead/remoteframeworkclasses/src/processor/res/parcelablewrappers/NullParcelableRemoteDevicePolicyManager.java.txt
@@ -39,6 +39,11 @@
     public static <F> NullParcelableRemoteDevicePolicyManager of(
             Bundler bundler, BundlerType type,
             RemoteDevicePolicyManager remoteDevicePolicyManager) {
+
+        if (remoteDevicePolicyManager != null) {
+            throw new IllegalArgumentException("remoteDevicePolicyManager can only be null");
+        }
+
         return new NullParcelableRemoteDevicePolicyManager();
     }
 
diff --git a/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh b/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
index 04ec558..7a5c6f3 100755
--- a/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
+++ b/common/device-side/bedstead/remoteframeworkclasses/update-apis.sh
@@ -14,6 +14,6 @@
 # limitations under the License.
 #
 
-cp ../../../../../packages/modules/Wifi/framework/api/current.txt src/processor/res/wifi-current.txt
-cp ../../../../../frameworks/base/core/api/current.txt src/processor/res/current.txt
-cp ../../../../../frameworks/base/core/api/test-current.txt src/processor/res/test-current.txt
\ No newline at end of file
+cp ../../../../../packages/modules/Wifi/framework/api/current.txt src/processor/res/apis/wifi-current.txt
+cp ../../../../../frameworks/base/core/api/current.txt src/processor/res/apis/current.txt
+cp ../../../../../frameworks/base/core/api/test-current.txt src/processor/res/apis/test-current.txt
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/Android.bp b/common/device-side/bedstead/testapp/Android.bp
index bf92e97..c297949 100644
--- a/common/device-side/bedstead/testapp/Android.bp
+++ b/common/device-side/bedstead/testapp/Android.bp
@@ -99,12 +99,12 @@
 
 java_genrule {
     name: "TestApp_Apps",
-    srcs: [":EmptyTestApp", ":EmptyTestApp2", ":DeviceAdminTestApp", ":LockTaskApp", ":DelegateTestApp", ":RemoteDPCTestApp", ":SmsApp", ":AccountManagementApp"],
+    srcs: [":EmptyTestApp", ":NotEmptyTestApp", ":DeviceAdminTestApp", ":LockTaskApp", ":DelegateTestApp", ":RemoteDPCTestApp", ":SmsApp", ":AccountManagementApp"],
     out: ["TestApp_Apps.res.zip"],
     tools: ["soong_zip", "index_testapps", "aapt2"],
     cmd: "mkdir -p $(genDir)/res/raw"
          + " && cp $(location :EmptyTestApp) $(genDir)/res/raw"
-         + " && cp $(location :EmptyTestApp2) $(genDir)/res/raw"
+         + " && cp $(location :NotEmptyTestApp) $(genDir)/res/raw"
          + " && cp $(location :DeviceAdminTestApp) $(genDir)/res/raw"
          + " && cp $(location :LockTaskApp) $(genDir)/res/raw"
          + " && cp $(location :DelegateTestApp) $(genDir)/res/raw"
@@ -125,11 +125,12 @@
 }
 
 android_test_helper_app {
-    name: "EmptyTestApp2",
+    name: "NotEmptyTestApp",
     static_libs: [
         "TestApp_TestApps"
     ],
-    manifest: "manifests/EmptyTestApp2Manifest.xml",
+    manifest: "manifests/NotEmptyTestAppManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -140,6 +141,7 @@
         "DeviceAdminApp"
     ],
     manifest: "manifests/DeviceAdminManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -149,6 +151,7 @@
         "TestApp_TestApps"
     ],
     manifest: "manifests/LockTaskAppManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -158,6 +161,7 @@
         "TestApp_TestApps"
     ],
     manifest: "manifests/DelegateManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -168,6 +172,7 @@
         "DeviceAdminApp"
     ],
     manifest: "manifests/RemoteDPCManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -177,6 +182,7 @@
         "TestApp_TestApps"
     ],
     manifest: "manifests/SmsAppManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
@@ -187,6 +193,7 @@
     ],
     resource_dirs: ["src/testapps/main/res/accountmanagement"],
     manifest: "manifests/AccountManagementManifest.xml",
+    additional_manifests: ["CommonManifest.xml"],
     min_sdk_version: "28"
 }
 
diff --git a/common/device-side/bedstead/testapp/CommonManifest.xml b/common/device-side/bedstead/testapp/CommonManifest.xml
new file mode 100644
index 0000000..4021535
--- /dev/null
+++ b/common/device-side/bedstead/testapp/CommonManifest.xml
@@ -0,0 +1,57 @@
+<?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.TestApp" android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
+    <uses-permission android:name="android.permission.READ_CONTACTS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
+    <uses-permission android:name="android.permission.READ_SMS" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.READ_CALENDAR" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+
+    <application
+        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
+
+        <activity android:name="android.testapp.activity" android:exported="true" />
+
+        <activity android:name="android.testapp.CrossProfileSharingActivity"
+                  android:exported="true">
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="com.android.testapp.SOME_ACTION"/>
+            </intent-filter>
+            <!-- Catch ACTION_PICK in case there is no other app handing it-->
+            <intent-filter>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.intent.action.PICK"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="com.android.bedstead.testapp.TestAppBroadcastController"
+                  android:exported="true" />
+    </application>
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+</manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml b/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
index 09256e5..2d7bc40 100644
--- a/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/AccountManagementManifest.xml
@@ -20,7 +20,6 @@
           package="com.android.bedstead.testapp.AccountManagementApp">
 
     <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
         android:testOnly="true">
 
         <service android:name=".TestAppAccountAuthenticatorService" android:exported="true">
diff --git a/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml b/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
index 5bd3772..6740e5e 100644
--- a/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/DelegateManifest.xml
@@ -18,13 +18,9 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.Delegate" android:targetSandboxVersion="2">
-
-    <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
-
+    <application>
         <!-- Don't allow this test app to be returned by queries unless filtered by package name -->
         <meta-data android:name="testapp-package-query-only" android:value="true" />
-
     </application>
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
 </manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml b/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
index 018de06..4ac4d4a 100644
--- a/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/DeviceAdminManifest.xml
@@ -18,12 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bedstead.testapp.DeviceAdminTestApp">
-    <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
-        android:testOnly="true">
-
-        <activity android:name="android.testapp.activity" android:exported="true" />
-
+    <application android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
         <receiver android:name=".DeviceAdminReceiver"
                   android:permission="android.permission.BIND_DEVICE_ADMIN"
                   android:exported="true">
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
deleted file mode 100644
index 778d8d0..0000000
--- a/common/device-side/bedstead/testapp/manifests/EmptyTestApp2Manifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?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.EmptyTestApp2">
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
-    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
-
-    <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
-
-        <activity android:name="android.testapp.activity" android:exported="true" />
-
-        <activity android:name="android.testapp.CrossProfileSharingActivity"
-                  android:exported="true">
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="com.android.testapp.SOME_ACTION"/>
-            </intent-filter>
-            <!-- Catch ACTION_PICK in case there is no other app handing it-->
-            <intent-filter>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <action android:name="android.intent.action.PICK"/>
-            </intent-filter>
-        </activity>
-    </application>
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
-</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
index ed0290f..1edf39d 100644
--- a/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/EmptyTestAppManifest.xml
@@ -18,20 +18,6 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bedstead.testapp.EmptyTestApp">
-
-    <uses-permission android:name="android.permission.READ_SMS" />
-    <uses-permission android:name="android.permission.BODY_SENSORS" />
-    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
-    <uses-permission android:name="android.permission.CAMERA" />
-    <uses-permission android:name="android.permission.READ_CALENDAR" />
-    <uses-permission android:name="android.permission.READ_CONTACTS" />
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
-    <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory">
-        <meta-data android:name="test-metadata-key" android:value="test-metadata-value" />
+    <application>
     </application>
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
 </manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml b/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
index 09685225..ff8ea1c5 100644
--- a/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/LockTaskAppManifest.xml
@@ -19,7 +19,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bedstead.testapp.LockTaskApp">
     <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
         android:testOnly="true">
 
         <activity android:name="android.testapp.ifwhitelistedactivity" android:lockTaskMode="if_whitelisted" android:exported="true" />
diff --git a/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml b/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml
new file mode 100644
index 0000000..2eee159
--- /dev/null
+++ b/common/device-side/bedstead/testapp/manifests/NotEmptyTestAppManifest.xml
@@ -0,0 +1,26 @@
+<?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.NotEmptyTestApp">
+
+    <application>
+        <meta-data android:name="test-metadata-key" android:value="test-metadata-value" />
+    </application>
+    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
+</manifest>
diff --git a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
index 395b5a5..f107562 100644
--- a/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/RemoteDPCManifest.xml
@@ -18,9 +18,6 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.RemoteDPC" android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.INTERACT_ACROSS_PROFILES" />
-
     <application
         android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
         android:testOnly="true">
@@ -37,7 +34,6 @@
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
             </intent-filter>
         </receiver>
-
     </application>
     <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28"/>
 </manifest>
\ No newline at end of file
diff --git a/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml b/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
index 2cbde3c..e68722c 100644
--- a/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
+++ b/common/device-side/bedstead/testapp/manifests/SmsAppManifest.xml
@@ -18,11 +18,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.bedstead.testapp.SmsApp">
-
-    <uses-permission android:name="android.permission.READ_SMS"/>
-
     <application
-        android:appComponentFactory="com.android.bedstead.testapp.TestAppAppComponentFactory"
         android:testOnly="true">
 
         <!-- BroadcastReceiver that listens for incoming SMS messages -->
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
index ac75967..eb4c8aa 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestApp.java
@@ -143,9 +143,18 @@
         return new TestAppInstance(this, user);
     }
 
-    private byte[] apkBytes() {
-        try (InputStream inputStream =
-                     sContext.getResources().openRawResource(mDetails.mResourceIdentifier)) {
+    /**
+     * Returns an {@link InputStream} of the apk for this app.
+     */
+    public InputStream apkStream() {
+        return sContext.getResources().openRawResource(mDetails.mResourceIdentifier);
+    }
+
+    /**
+     * Returns a byte array of the apk for this app.
+     */
+    public byte[] apkBytes() {
+        try (InputStream inputStream = apkStream()) {
             return readInputStreamFully(inputStream);
         } catch (IOException e) {
             throw new NeneException("Error when reading TestApp bytes", e);
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
index 68cf755..fb5b505 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppInstance.java
@@ -150,19 +150,30 @@
      * will have the same effect as calling {@link #keepAlive()}.
      */
     public void registerReceiver(IntentFilter intentFilter) {
+        registerReceiver(intentFilter, 0);
+    }
+
+    /**
+     * See {@link registerReceiver(IntentFilter)}.
+     */
+    public void registerReceiver(IntentFilter intentFilter, int flags) {
         if (mRegisteredBroadcastReceivers.containsKey(intentFilter)) {
             return;
         }
 
         long receiverId = UUID.randomUUID().getMostSignificantBits();
-        registerReceiver(intentFilter, receiverId);
+        registerReceiver(intentFilter, receiverId, flags);
         keepAlive(/* manualKeepAlive= */ false);
     }
 
     private void registerReceiver(IntentFilter intentFilter, long receiverId) {
+        registerReceiver(intentFilter, receiverId, 0);
+    }
+
+    private void registerReceiver(IntentFilter intentFilter, long receiverId, int flags) {
         try {
             mConnector.connect();
-            mTestAppController.other().registerReceiver(receiverId, intentFilter);
+            mTestAppController.other().registerReceiver(receiverId, intentFilter, flags);
             mRegisteredBroadcastReceivers.put(intentFilter, receiverId);
         } catch (UnavailableProfileException e) {
             throw new IllegalStateException("Could not connect to test app", e);
diff --git a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
index 5afc802..b6e2531 100644
--- a/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
+++ b/common/device-side/bedstead/testapp/src/library/main/java/com/android/bedstead/testapp/TestAppQueryBuilder.java
@@ -48,6 +48,7 @@
             new SetQueryHelper<>(this);
     SetQueryHelper<TestAppQueryBuilder, ServiceInfo, ServiceQuery<?>> mServices =
             new SetQueryHelper<>(this);
+    BooleanQueryHelper<TestAppQueryBuilder> mIsDeviceAdmin = new BooleanQueryHelper<>(this);
     StringQueryHelper<TestAppQueryBuilder> mSharedUserId = new StringQueryHelper<>(this);
 
     TestAppQueryBuilder(TestAppProvider provider) {
@@ -111,6 +112,13 @@
     }
 
     /**
+     * Query for an app which is a device admin.
+     */
+    public BooleanQuery<TestAppQueryBuilder> whereIsDeviceAdmin() {
+        return mIsDeviceAdmin;
+    }
+
+    /**
      * Query for a {@link TestApp} by its sharedUserId;
      */
     public StringQuery<TestAppQueryBuilder> whereSharedUserId() {
@@ -194,6 +202,13 @@
             return false;
         }
 
+        // TODO(b/198419895): Actually query for the correct receiver + metadata
+        boolean isDeviceAdmin = details.mApp.getPackageName().equals(
+                "com.android.bedstead.testapp.DeviceAdminTestApp");
+        if (!BooleanQueryHelper.matches(mIsDeviceAdmin, isDeviceAdmin)) {
+            return false;
+        }
+
         if (mSharedUserId.isEmpty()) {
             if (details.sharedUserId() != null) {
                 return false;
@@ -226,7 +241,8 @@
                 mServices.describeQuery("services"),
                 mPermissions.describeQuery("permissions"),
                 mSharedUserId.describeQuery("sharedUserId"),
-                mTestOnly.describeQuery("testOnly")
+                mTestOnly.describeQuery("testOnly"),
+                mIsDeviceAdmin.describeQuery("isDeviceAdmin")
         ) + "}";
     }
 }
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
index bc270b4..3718237 100644
--- 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
@@ -234,6 +234,26 @@
     }
 
     @Test
+    public void query_isDeviceAdmin_returnsMatching() {
+        TestApp testApp = mTestAppProvider.query()
+                .whereIsDeviceAdmin().isTrue()
+                .get();
+
+        assertThat(testApp.packageName()).isEqualTo(
+                "com.android.bedstead.testapp.DeviceAdminTestApp");
+    }
+
+    @Test
+    public void query_isNotDeviceAdmin_returnsMatching() {
+        TestApp testApp = mTestAppProvider.query()
+                .whereIsDeviceAdmin().isFalse()
+                .get();
+
+        assertThat(testApp.packageName()).isNotEqualTo(
+                "com.android.bedstead.testapp.DeviceAdminTestApp");
+    }
+
+    @Test
     public void query_doesNotSpecifySharedUserId_sharedUserIdIsNull() {
         TestApp testApp = mTestAppProvider.query()
                 .get();
diff --git a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java
new file mode 100644
index 0000000..554567c
--- /dev/null
+++ b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppBroadcastController.java
@@ -0,0 +1,36 @@
+/*
+ * 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.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.compatibility.common.util.enterprise.DeviceAdminReceiverUtils;
+
+/**
+ * {@link BroadcastReceiver} allowing for simple control of test apps using broadcasts.
+ */
+public final class TestAppBroadcastController extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        setResultCode(Activity.RESULT_OK);
+        if (DeviceAdminReceiverUtils.disableSelf(context, intent)) return;
+    }
+}
diff --git a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
index 06c5c1e..08113b0 100644
--- a/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
+++ b/common/device-side/bedstead/testapp/src/testapps/main/java/com/android/bedstead/testapp/TestAppController.java
@@ -39,10 +39,19 @@
      */
     @CrossUser
     public void registerReceiver(Context context, long receiverId, IntentFilter intentFilter) {
+        registerReceiver(context, receiverId, intentFilter, 0);
+    }
+
+    /**
+     * See {@link registerReceiver(Context, long, IntentFilter)}.
+     */
+    @CrossUser
+    public void registerReceiver(Context context, long receiverId, IntentFilter intentFilter,
+            int flags) {
         BaseTestAppBroadcastReceiver broadcastReceiver = new BaseTestAppBroadcastReceiver();
         sBroadcastReceivers.put(receiverId, broadcastReceiver);
 
-        context.registerReceiver(broadcastReceiver, intentFilter);
+        context.registerReceiver(broadcastReceiver, intentFilter, flags);
     }
 
     /**
diff --git a/common/device-side/util-axt/Android.bp b/common/device-side/util-axt/Android.bp
index 5357ce5..11f851b 100644
--- a/common/device-side/util-axt/Android.bp
+++ b/common/device-side/util-axt/Android.bp
@@ -49,6 +49,5 @@
     name: "compatibility-device-util-nodeps",
     srcs: [
          "src/com/android/compatibility/common/util/IBinderParcelable.java",
-         "src/com/android/compatibility/common/util/ImeAwareEditText.java",
     ],
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
index 49208b1..3655fb0 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BaseDefaultPermissionGrantPolicyTest.java
@@ -105,6 +105,12 @@
             addSplitFromNonDangerousPermissions(packagesToVerify, pregrantUidStates);
         }
 
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
+                || ApiLevelUtil.codenameStartsWith("T")) {
+            addImplicitlyGrantedPermission(Manifest.permission.POST_NOTIFICATIONS,
+                    Build.VERSION_CODES.TIRAMISU, packagesToVerify, pregrantUidStates);
+        }
+
         // Add exceptions
         addExceptionsDefaultPermissions(packagesToVerify, runtimePermNames, pregrantUidStates);
 
@@ -507,6 +513,37 @@
         }
     }
 
+    /**
+     * Add a permission which is granted, if it is implicitly added to a package
+     * @param permissionToAdd The permission to be added
+     * @param targetSdk The targetSDK below which the permission is added in
+     * @param packageInfos The packageInfos to be checked
+     * @param outUidStates The state to be modified
+     */
+    public static void addImplicitlyGrantedPermission(String permissionToAdd, int targetSdk,
+            Map<String, PackageInfo> packageInfos, SparseArray<UidState> outUidStates) {
+        for (PackageInfo pkg : packageInfos.values()) {
+            if (pkg.applicationInfo.targetSdkVersion >= targetSdk) {
+                continue;
+            }
+            for (String perm: pkg.requestedPermissions) {
+                if (perm.equals(permissionToAdd)) {
+                    int uid = pkg.applicationInfo.uid;
+                    UidState uidState = outUidStates.get(uid);
+                    if (uidState != null
+                            && uidState.grantedPermissions.containsKey(permissionToAdd)) {
+                        // permission is already granted. Don't override the grant-state.
+                        continue;
+                    }
+
+                    appendPackagePregrantedPerms(pkg, "permission " + permissionToAdd
+                                    + " is granted to pre-" + targetSdk + " apps", false,
+                            Collections.singleton(permissionToAdd), outUidStates);
+                }
+            }
+        }
+    }
+
     public static void appendPackagePregrantedPerms(PackageInfo packageInfo, String reason,
             boolean fixed, Set<String> pregrantedPerms, SparseArray<UidState> outUidStates) {
         final int uid = packageInfo.applicationInfo.uid;
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 6d44391..b68578f 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
@@ -19,11 +19,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import androidx.annotation.Nullable;
 import android.util.Log;
 
-import java.util.ArrayList;
-import java.util.List;
+import androidx.annotation.Nullable;
+
 import java.util.Set;
 import java.util.concurrent.ArrayBlockingQueue;
 import java.util.concurrent.BlockingQueue;
@@ -78,25 +77,33 @@
         return create(context, intentFilter, /* checker= */ null);
     }
 
-    public static BlockingBroadcastReceiver create(Context context, String expectedAction, Function<Intent, Boolean> checker) {
+    public static BlockingBroadcastReceiver create(Context context, String expectedAction,
+            Function<Intent, Boolean> checker) {
         return create(context, new IntentFilter(expectedAction), checker);
     }
 
-    public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter, Function<Intent, Boolean> checker) {
+    public static BlockingBroadcastReceiver create(Context context, IntentFilter intentFilter,
+            Function<Intent, Boolean> checker) {
         return create(context, Set.of(intentFilter), checker);
     }
 
-    public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters) {
+    public static BlockingBroadcastReceiver create(Context context,
+            Set<IntentFilter> intentFilters) {
         return create(context, intentFilters, /* checker= */ null);
     }
 
-    public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters, Function<Intent, Boolean> checker) {
+    public static BlockingBroadcastReceiver create(Context context, Set<IntentFilter> intentFilters,
+            Function<Intent, Boolean> checker) {
         BlockingBroadcastReceiver blockingBroadcastReceiver =
                 new BlockingBroadcastReceiver(context, intentFilters, checker);
 
         return blockingBroadcastReceiver;
     }
 
+    public BlockingBroadcastReceiver(Context context) {
+        this(context, Set.of());
+    }
+
     public BlockingBroadcastReceiver(Context context, String expectedAction) {
         this(context, new IntentFilter(expectedAction));
     }
@@ -143,7 +150,8 @@
 
     public BlockingBroadcastReceiver register() {
         for (IntentFilter intentFilter : mIntentFilters) {
-            mContext.registerReceiver(this, intentFilter);
+            mContext.registerReceiver(this, intentFilter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         return this;
@@ -153,7 +161,8 @@
         for (IntentFilter intentFilter : mIntentFilters) {
             mContext.registerReceiverForAllUsers(
                     this, intentFilter, /* broadcastPermission= */ null,
-                    /* scheduler= */ null);
+                    /* scheduler= */ null,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         return this;
@@ -173,7 +182,8 @@
      * 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.
      */
-    public @Nullable Intent awaitForBroadcast() {
+    public @Nullable
+    Intent awaitForBroadcast() {
         return awaitForBroadcast(DEFAULT_TIMEOUT_SECONDS * 1000);
     }
 
@@ -193,7 +203,8 @@
      * 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.
      */
-    public @Nullable Intent awaitForBroadcast(long timeoutMillis) {
+    public @Nullable
+    Intent awaitForBroadcast(long timeoutMillis) {
         if (mReceivedIntent != null) {
             return mReceivedIntent;
         }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java
new file mode 100644
index 0000000..a5a12a4
--- /dev/null
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/BroadcastMessenger.java
@@ -0,0 +1,233 @@
+/*
+ * 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.compatibility.common.util;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.util.Log;
+
+import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Objects;
+
+/**
+ * Provides a one-way communication mechanism using a Parcelable as a payload, via broadcasts.
+ *
+ * Use {@link #send(Context, String, Parcelable)} to send a message.
+ * Use {@link Receiver} to receive a message.
+ *
+ * Pick a unique "suffix" for your test, and use it with both the sender and receiver, in order
+ * to avoid "cross-talks" between different tests. (if they ever run at the same time.)
+ */
+public final class BroadcastMessenger {
+    private static final String TAG = "BroadcastMessenger";
+
+    private static final String ACTION_MESSAGE =
+            "com.android.compatibility.common.util.BroadcastMessenger.ACTION_MESSAGE_";
+    private static final String ACTION_PING =
+            "com.android.compatibility.common.util.BroadcastMessenger.ACTION_PING_";
+    private static final String EXTRA_MESSAGE =
+            "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_MESSAGE";
+
+    /**
+     * We need to drop messages that were sent before the receiver was created. We keep
+     * track of the message send time in this extra.
+     */
+    private static final String EXTRA_SENT_TIME =
+            "com.android.compatibility.common.util.BroadcastMessenger.EXTRA_SENT_TIME";
+
+    public static final int DEFAULT_TIMEOUT_MS = 10_000;
+
+    private static long getCurrentTime() {
+        return SystemClock.uptimeMillis();
+    }
+
+    private static void sendBroadcast(@NonNull Intent i, @NonNull Context context,
+            @NonNull String broadcastSuffix, @Nullable String receiverPackage) {
+        i.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        i.setPackage(receiverPackage);
+        i.putExtra(EXTRA_SENT_TIME, getCurrentTime());
+
+        context.sendBroadcast(i);
+    }
+
+    /** Send a message to the {@link Receiver} expecting a given "suffix". */
+    public static <T extends Parcelable> void send(@NonNull Context context,
+            @NonNull String broadcastSuffix, @NonNull T message) {
+        final Intent i = new Intent(ACTION_MESSAGE + Objects.requireNonNull(broadcastSuffix));
+        i.putExtra(EXTRA_MESSAGE, Objects.requireNonNull(message));
+
+        Log.i(TAG, "Sending: " + message);
+        sendBroadcast(i, context, broadcastSuffix, /*receiverPackage=*/ null);
+    }
+
+    private static void sendPing(@NonNull Context context, @NonNull String broadcastSuffix,
+            @NonNull String receiverPackage) {
+        final Intent i = new Intent(ACTION_PING + Objects.requireNonNull(broadcastSuffix));
+
+        Log.i(TAG, "Sending a ping");
+        sendBroadcast(i, context, broadcastSuffix, receiverPackage);
+    }
+
+    /**
+     * Receive messages sent with {@link #send}. Note it'll ignore all the messages that were
+     * sent before instantiated.
+     *
+     * @param <T> the class that encapsulates the message.
+     */
+    public static final class Receiver<T extends Parcelable> implements AutoCloseable {
+        private final Context mContext;
+        private final String mBroadcastSuffix;
+        private final HandlerThread mReceiverThread = new HandlerThread(TAG);
+        private final Handler mReceiverHandler;
+
+        @GuardedBy("mMessages")
+        private final ArrayList<T> mMessages = new ArrayList<>();
+        private final long mCreatedTime = getCurrentTime();
+        private boolean mRegistered;
+
+        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                // Log.d(TAG, "Received intent: " + intent);
+                if (intent.getAction().equals(ACTION_MESSAGE + mBroadcastSuffix)
+                        || intent.getAction().equals(ACTION_PING + mBroadcastSuffix)) {
+                    // OK
+                } else {
+                    throw new RuntimeException("Unknown broadcast received: " + intent);
+                }
+                if (intent.getLongExtra(EXTRA_SENT_TIME, 0) < mCreatedTime) {
+                    Log.i(TAG, "Dropping stale broadcast: " + intent);
+                    return;
+                }
+
+                // Note for a PING, the message will be null.
+                final T message = intent.getParcelableExtra(EXTRA_MESSAGE);
+                if (message != null) {
+                    Log.i(TAG, "Received: " + message);
+                }
+
+                synchronized (mMessages) {
+                    mMessages.add(message);
+                    mMessages.notifyAll();
+                }
+            }
+        };
+
+        /**
+         * Constructor.
+         */
+        public Receiver(@NonNull Context context, @NonNull String broadcastSuffix) {
+            mContext = context;
+            mBroadcastSuffix = Objects.requireNonNull(broadcastSuffix);
+
+            mReceiverThread.start();
+            mReceiverHandler = new Handler(mReceiverThread.getLooper());
+
+            final IntentFilter fi = new IntentFilter(ACTION_MESSAGE + mBroadcastSuffix);
+            fi.addAction(ACTION_PING + mBroadcastSuffix);
+
+            context.registerReceiver(mReceiver, fi, /* permission=*/ null,
+                    mReceiverHandler, Context.RECEIVER_EXPORTED);
+            mRegistered = true;
+        }
+
+        @Override
+        public void close() {
+            if (mRegistered) {
+                mContext.unregisterReceiver(mReceiver);
+                mReceiverThread.quit();
+                mRegistered = false;
+            }
+        }
+
+        /**
+         * Receive the next message with a 10 second timeout.
+         */
+        @NonNull
+        public T waitForNextMessage() {
+            return waitForNextMessage(DEFAULT_TIMEOUT_MS);
+        }
+
+        /**
+         * Receive the next message.
+         */
+        @NonNull
+        public T waitForNextMessage(long timeoutMillis) {
+            final T message = waitForNextMessageOrPing(timeoutMillis);
+            if (message == null) {
+                throw new RuntimeException("Received unexpected ACTION_PING");
+            }
+            return message;
+        }
+
+        /**
+         * Internal method, either return the next message, or null when a PING broadcast
+         * is received.
+         */
+        @Nullable
+        private T waitForNextMessageOrPing(long timeoutMillis) {
+            final long timeout = System.currentTimeMillis() + timeoutMillis;
+            synchronized (mMessages) {
+                while (mMessages.size() == 0) {
+                    final long wait = timeout - System.currentTimeMillis();
+                    if (wait <= 0) {
+                        throw new RuntimeException("Timeout waiting for the next message");
+                    }
+                    try {
+                        mMessages.wait(wait);
+                    } catch (InterruptedException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+                return mMessages.remove(0);
+            }
+        }
+
+        /**
+         * Ensure that no further messages have been received.
+         *
+         * Call it before {@link #close()}.
+         */
+        public void ensureNoMoreMessages() {
+            // If there's a message already in mMessages, then we know it'll fail, so we don't
+            // need to send a ping.
+            // OTOH, even if there's no message enqueued, there may be broadcasts already enqueued,
+            // so we send a "ping" message,
+            synchronized (mMessages) {
+                if (mMessages.size() == 0) {
+                    // Send a ping to myself.
+                    sendPing(mContext, mBroadcastSuffix, mContext.getPackageName());
+                }
+            }
+
+            final T m = waitForNextMessageOrPing(DEFAULT_TIMEOUT_MS);
+            if (m == null) {
+                return; // Okay. Ping will deliver a null message.
+            }
+            throw new RuntimeException("No more messages expected, but received: " + m);
+        }
+    }
+}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
index e722fb2..9c27e23 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/CallbackAsserter.java
@@ -101,7 +101,8 @@
                     mLatch.countDown();
                 }
             };
-            InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
+            InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter,
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         @Override
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
deleted file mode 100644
index f28d085..0000000
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/ImeAwareEditText.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.compatibility.common.util;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-
-public class ImeAwareEditText extends EditText {
-    private boolean mHasPendingShowSoftInputRequest;
-    final Runnable mRunShowSoftInputIfNecessary = () -> showSoftInputIfNecessary();
-
-    public ImeAwareEditText(Context context) {
-        super(context, null);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs) {
-        super(context, attrs);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr) {
-        super(context, attrs, defStyleAttr);
-    }
-
-    public ImeAwareEditText(Context context, AttributeSet attrs, int defStyleAttr,
-            int defStyleRes) {
-        super(context, attrs, defStyleAttr, defStyleRes);
-    }
-
-    /**
-     * This method is called back by the system when the system is about to establish a connection
-     * to the current input method.
-     *
-     * <p>This is a good and reliable signal to schedule a pending task to call
-     * {@link InputMethodManager#showSoftInput(View, int)}.</p>
-     *
-     * @param editorInfo context about the text input field.
-     * @return {@link InputConnection} to be passed to the input method.
-     */
-    @Override
-    public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
-        final InputConnection ic = super.onCreateInputConnection(editorInfo);
-        if (mHasPendingShowSoftInputRequest) {
-            removeCallbacks(mRunShowSoftInputIfNecessary);
-            post(mRunShowSoftInputIfNecessary);
-        }
-        return ic;
-    }
-
-    private void showSoftInputIfNecessary() {
-        if (mHasPendingShowSoftInputRequest) {
-            final InputMethodManager imm =
-                    getContext().getSystemService(InputMethodManager.class);
-            imm.showSoftInput(this, 0);
-            mHasPendingShowSoftInputRequest = false;
-        }
-    }
-
-    public void scheduleShowSoftInput() {
-        final InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
-        if (imm.hasActiveInputConnection(this)) {
-            // This means that ImeAwareEditText is already connected to the IME.
-            // InputMethodManager#showSoftInput() is guaranteed to pass client-side focus check.
-            mHasPendingShowSoftInputRequest = false;
-            removeCallbacks(mRunShowSoftInputIfNecessary);
-            imm.showSoftInput(this, 0);
-            return;
-        }
-
-        // Otherwise, InputMethodManager#showSoftInput() should be deferred after
-        // onCreateInputConnection().
-        mHasPendingShowSoftInputRequest = true;
-    }
-}
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
index 639c871..b344154 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/UiAutomatorUtils.java
@@ -98,9 +98,15 @@
                     } else {
                         Rect boundsBeforeScroll = scrollable.getBounds();
                         boolean scrollAtStartOrEnd = !scrollable.scrollForward();
-                        Rect boundsAfterScroll = scrollable.getBounds();
-                        isAtEnd = scrollAtStartOrEnd && boundsBeforeScroll.equals(
-                                boundsAfterScroll);
+                        // The scrollable view may no longer be scrollable after the toolbar is
+                        // collapsed.
+                        if (scrollable.exists()) {
+                            Rect boundsAfterScroll = scrollable.getBounds();
+                            isAtEnd = scrollAtStartOrEnd && boundsBeforeScroll.equals(
+                                    boundsAfterScroll);
+                        } else {
+                            isAtEnd = scrollAtStartOrEnd;
+                        }
                     }
                 } else {
                     // There might be a collapsing toolbar, but no scrollable view. Try to collapse
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
index b6473b6..60d7535 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/devicepolicy/provisioning/SilentProvisioningTestManager.java
@@ -163,7 +163,8 @@
         }
 
         public void register() {
-            mContext.registerReceiver(this, new IntentFilter(mAction));
+            mContext.registerReceiver(this, new IntentFilter(mAction),
+                    Context.RECEIVER_EXPORTED_UNAUDITED);
         }
 
         public boolean await() throws InterruptedException {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
index d1d7dcb..c0c434e 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/DeviceAdminReceiverUtils.java
@@ -29,7 +29,7 @@
     private static final String TAG = DeviceAdminReceiverUtils.class.getSimpleName();
     private static final boolean DEBUG = false;
 
-    private static final String ACTION_DISABLE_SELF = "disable_self";
+    public static final String ACTION_DISABLE_SELF = "disable_self";
 
     /**
      * Disables itself as profile / owner upon receiving a {@value #ACTION_DISABLE_SELF} intent.
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
index cf88726..19b6194 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/enterprise/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
diff --git a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
index f1c053f..eb7bf3b 100644
--- a/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
+++ b/hostsidetests/angle/app/common/src/com/android/angleIntegrationTest/common/GlesView.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.angleIntegrationTest.common;
+package com.android.angleintegrationtest.common;
 
 import static javax.microedition.khronos.egl.EGL10.EGL_STENCIL_SIZE;
 
@@ -23,6 +23,7 @@
 import android.opengl.GLES20;
 import android.os.Build.VERSION_CODES;
 import android.util.Log;
+
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGLConfig;
 import javax.microedition.khronos.egl.EGLContext;
diff --git a/hostsidetests/angle/app/driverTest/AndroidManifest.xml b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
index 73392df..6fe6704 100755
--- a/hostsidetests/angle/app/driverTest/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTest/AndroidManifest.xml
@@ -16,13 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.angleIntegrationTest.driverTest"
+     package="com.android.angleintegrationtest.drivertest"
      android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+        <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -32,6 +32,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="com.android.angleIntegrationTest.driverTest"/>
+         android:targetPackage="com.android.angleintegrationtest.drivertest"/>
 
 </manifest>
diff --git a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
index 450461a..6eb169b 100644
--- a/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTest/src/com/android/angleIntegrationTest/driverTest/AngleDriverTestActivity.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.angleIntegrationTest.driverTest;
+package com.android.angleintegrationtest.drivertest;
 
 import static org.junit.Assert.fail;
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.angleIntegrationTest.common.GlesView;
+import com.android.angleintegrationtest.common.GlesView;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,7 +46,6 @@
 
     @Test
     public void testUseDefaultDriver() throws Exception {
-        // The rules file does not enable ANGLE for this app
         validateDeveloperOption(false);
     }
 
diff --git a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
index e88a8c3..70dcec4 100755
--- a/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
+++ b/hostsidetests/angle/app/driverTestSecondary/AndroidManifest.xml
@@ -16,13 +16,13 @@
  -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.angleIntegrationTest.driverTestSecondary"
+     package="com.android.angleintegrationtest.drivertestsecondary"
      android:targetSandboxVersion="2">
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner"/>
 
-        <activity android:name="com.android.angleIntegrationTest.common.AngleIntegrationTestActivity"
+        <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -32,6 +32,6 @@
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-         android:targetPackage="com.android.angleIntegrationTest.driverTestSecondary"/>
+         android:targetPackage="com.android.angleintegrationtest.drivertestsecondary"/>
 
 </manifest>
diff --git a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
index 7b25c39..b855f6b 100644
--- a/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
+++ b/hostsidetests/angle/app/driverTestSecondary/src/com/android/angleIntegrationTest/driverTestSecondary/AngleDriverTestActivity.java
@@ -14,13 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.angleIntegrationTest.driverTestSecondary;
+package com.android.angleintegrationtest.drivertestsecondary;
 
 import static org.junit.Assert.fail;
 
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.angleIntegrationTest.common.GlesView;
+import com.android.angleintegrationtest.common.GlesView;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -46,7 +46,6 @@
 
     @Test
     public void testUseDefaultDriver() throws Exception {
-        // The rules file does not enable ANGLE for this app
         validateDeveloperOption(false);
     }
 
diff --git a/hostsidetests/angle/app/gameDriverTest/Android.bp b/hostsidetests/angle/app/gameDriverTest/Android.bp
new file mode 100644
index 0000000..749f8cf
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/Android.bp
@@ -0,0 +1,36 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAngleGameDriverTestCases",
+    defaults: ["cts_support_defaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    compile_multilib: "both",
+    static_libs: [
+        "ctstestrunner-axt",
+        "androidx.test.rules",
+        "AngleIntegrationTestCommon",
+    ],
+}
diff --git a/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml b/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml
new file mode 100755
index 0000000..7b6b10a
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.angleintegrationtest.gamedrivertest"
+     android:targetSandboxVersion="2">
+
+    <application android:debuggable="true"
+                 android:appCategory="game">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="com.android.angleintegrationtest.common.AngleIntegrationTestActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="com.android.angleintegrationtest.gamedrivertest"/>
+
+</manifest>
diff --git a/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java b/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java
new file mode 100644
index 0000000..cec4d2a
--- /dev/null
+++ b/hostsidetests/angle/app/gameDriverTest/src/com/android/angleIntegrationTest/gameDriverTest/AngleDriverTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * 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.angleintegrationtest.gamedrivertest;
+
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.angleintegrationtest.common.GlesView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AngleDriverTestActivity {
+
+    private final String mTAG = this.getClass().getSimpleName();
+
+    private void validateDeveloperOption(boolean angleEnabled) throws Exception {
+        GlesView glesView = new GlesView();
+
+        if (!glesView.validateDeveloperOption(angleEnabled)) {
+            if (angleEnabled) {
+                String renderer = glesView.getRenderer();
+                fail("Failure - ANGLE was not loaded: '" + renderer + "'");
+            } else {
+                String renderer = glesView.getRenderer();
+                fail("Failure - ANGLE was loaded: '" + renderer + "'");
+            }
+        }
+    }
+
+    @Test
+    public void testUseDefaultDriver() throws Exception {
+        validateDeveloperOption(false);
+    }
+
+    @Test
+    public void testUseAngleDriver() throws Exception {
+        validateDeveloperOption(true);
+    }
+
+    @Test
+    public void testUseNativeDriver() throws Exception {
+        validateDeveloperOption(false);
+    }
+}
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
index ba33966..0580d3a 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleCommon.java
@@ -36,23 +36,20 @@
     // System Properties
     static final String PROPERTY_TEMP_RULES_FILE = "debug.angle.rules";
 
-    // Rules File
-    static final String DEVICE_TEMP_RULES_FILE_DIRECTORY = "/data/local/tmp";
-    static final String DEVICE_TEMP_RULES_FILE_FILENAME = "a4a_rules.json";
-    static final String DEVICE_TEMP_RULES_FILE_PATH =
-            DEVICE_TEMP_RULES_FILE_DIRECTORY + "/" + DEVICE_TEMP_RULES_FILE_FILENAME;
-
     // ANGLE
     static final String ANGLE_PACKAGE_NAME = "com.android.angle";
-    static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleIntegrationTest.driverTest";
+    static final String ANGLE_DRIVER_TEST_PKG = "com.android.angleintegrationtest.drivertest";
     static final String ANGLE_DRIVER_TEST_SEC_PKG =
-            "com.android.angleIntegrationTest.driverTestSecondary";
+            "com.android.angleintegrationtest.drivertestsecondary";
+    static final String ANGLE_GAME_DRIVER_TEST_PKG =
+            "com.android.angleintegrationtest.gamedrivertest";
     static final String ANGLE_DRIVER_TEST_CLASS = "AngleDriverTestActivity";
     static final String ANGLE_DRIVER_TEST_DEFAULT_METHOD = "testUseDefaultDriver";
     static final String ANGLE_DRIVER_TEST_ANGLE_METHOD = "testUseAngleDriver";
     static final String ANGLE_DRIVER_TEST_NATIVE_METHOD = "testUseNativeDriver";
     static final String ANGLE_DRIVER_TEST_APP = "CtsAngleDriverTestCases.apk";
     static final String ANGLE_DRIVER_TEST_SEC_APP = "CtsAngleDriverTestCasesSecondary.apk";
+    static final String ANGLE_GAME_DRIVER_TEST_APP = "CtsAngleGameDriverTestCases.apk";
     static final String ANGLE_DUMPSYS_GPU_TEST_PKG =
             "com.android.angleintegrationtest.dumpsysgputest";
     static final String ANGLE_DUMPSYS_GPU_TEST_CLASS = "AngleDumpsysGpuTestActivity";
@@ -145,6 +142,26 @@
         device.executeShellCommand("setprop " + property + " " + value);
     }
 
+    static void setGameModeBatteryConfig(ITestDevice device, String packageName, boolean useAngle)
+            throws Exception {
+        device.executeShellCommand("device_config put game_overlay " + packageName
+                + " mode=3,useAngle=" + Boolean.toString(useAngle));
+    }
+
+    static void setGameModeStandardConfig(ITestDevice device, String packageName, boolean useAngle)
+            throws Exception {
+        device.executeShellCommand("device_config put game_overlay " + packageName
+                + " mode=1,useAngle=" + Boolean.toString(useAngle));
+    }
+
+    static void setGameModeBattery(ITestDevice device, String packageName) throws Exception {
+        device.executeShellCommand("cmd game mode battery " + packageName);
+    }
+
+    static void setGameModeStandard(ITestDevice device, String packageName) throws Exception {
+        device.executeShellCommand("cmd game mode standard " + packageName);
+    }
+
     /**
      * Find and parse the `dumpsys gpu` output for the specified package.
      *
diff --git a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
index 790db4d..98f81b9 100644
--- a/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
+++ b/hostsidetests/angle/src/android/angle/cts/CtsAngleDeveloperOptionHostTest.java
@@ -88,6 +88,7 @@
 
         stopPackage(getDevice(), ANGLE_DRIVER_TEST_PKG);
         stopPackage(getDevice(), ANGLE_DRIVER_TEST_SEC_PKG);
+        stopPackage(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
     }
 
     @After
@@ -354,6 +355,98 @@
     }
 
     /**
+     * Test ANGLE is loaded when the Battery Game Mode includes 'useAngle=true'.
+     */
+    @Test
+    public void testGameModeBatteryUseAngleDriver() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+        setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+        setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_ANGLE_METHOD);
+    }
+
+    /**
+     * Test ANGLE is loaded when the Standard Game Mode includes 'useAngle=true'.
+     */
+    @Test
+    public void testGameModeStandardUseAngleDriver() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+        setGameModeStandardConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+        setGameModeStandard(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_ANGLE_METHOD);
+    }
+
+    /**
+     * Test setting the Game Mode to use ANGLE ('useAngle=true') and then overriding that to use the
+     * native driver with the Global.Settings loads the native driver.
+     */
+    @Test
+    public void testGameModeBatteryUseAngleOverrideWithNative() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+        // Set Game Mode to use ANGLE and verify ANGLE is loaded.
+        setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, true);
+        setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_ANGLE_METHOD);
+
+        // Set Global.Settings to use the native driver and verify the native driver is loaded.
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG,
+                sDriverGlobalSettingMap.get(OpenGlDriverChoice.NATIVE));
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_NATIVE_METHOD);
+    }
+
+    /**
+     * Test setting the Game Mode to not use ANGLE ('useAngle=false') and then overriding that to
+     * use ANGLE with the Global.Settings loads ANGLE.
+     */
+    @Test
+    public void testGameModeBatteryDontUseAngleOverrideWithAngle() throws Exception {
+        Assume.assumeTrue(isAngleInstalled(getDevice()));
+        Assume.assumeFalse(isNativeDriverAngle(getDevice()));
+
+        installApp(ANGLE_GAME_DRIVER_TEST_APP);
+
+        // Set Game Mode to *not* use ANGLE and verify the native driver is loaded.
+        setGameModeBatteryConfig(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG, false);
+        setGameModeBattery(getDevice(), ANGLE_GAME_DRIVER_TEST_PKG);
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_NATIVE_METHOD);
+
+        // Set Global.Settings to use ANGLE and verify ANGLE is loaded.
+        setAndValidateAngleDevOptionPkgDriver(ANGLE_GAME_DRIVER_TEST_PKG,
+                sDriverGlobalSettingMap.get(OpenGlDriverChoice.ANGLE));
+
+        runDeviceTests(ANGLE_GAME_DRIVER_TEST_PKG,
+                ANGLE_GAME_DRIVER_TEST_PKG + "." + ANGLE_DRIVER_TEST_CLASS,
+                ANGLE_DRIVER_TEST_ANGLE_METHOD);
+    }
+
+    /**
      * Test that the `dumpsys gpu` correctly indicates `angleInUse = 1` when ANGLE is enabled.
      */
     @Test
diff --git a/hostsidetests/appcloning/Android.bp b/hostsidetests/appcloning/Android.bp
new file mode 100644
index 0000000..8b646fe
--- /dev/null
+++ b/hostsidetests/appcloning/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsAppCloningHostTest",
+    srcs: [
+        "hostside/src/**/AppCloningHostTest.java",
+        "hostside/src/**/BaseHostTestCase.java",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
+    test_config: "AndroidTestAppCloning.xml",
+    data: [":CtsAppCloningTestApp"],
+}
diff --git a/hostsidetests/appcloning/AndroidTestAppCloning.xml b/hostsidetests/appcloning/AndroidTestAppCloning.xml
new file mode 100644
index 0000000..8c882eb
--- /dev/null
+++ b/hostsidetests/appcloning/AndroidTestAppCloning.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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="Test for App cloning support with clone user profiles">
+    <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" />
+    <!-- Clone user profile is meant to exist only alongside a real system user.
+    It does not exist for a headless system user, or a secondary user -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.cts.appcloning.AppCloningHostTest" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/hostsidetests/appcloning/OWNERS b/hostsidetests/appcloning/OWNERS
new file mode 100644
index 0000000..7cc9314
--- /dev/null
+++ b/hostsidetests/appcloning/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1029024
+saumyap@google.com
+maco@google.com
+dagarhimanshu@google.com
+sarup@google.com
+sailendrabathi@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcloning/TEST_MAPPING b/hostsidetests/appcloning/TEST_MAPPING
new file mode 100644
index 0000000..f512df9
--- /dev/null
+++ b/hostsidetests/appcloning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppCloningHostTest"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
new file mode 100644
index 0000000..ce7356c
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.appcloning;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.contentprovider.ContentProviderHandler;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.util.CommandResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Runs the AppCloning tests.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+@AppModeFull
+public class AppCloningHostTest extends BaseHostTestCase {
+
+    private static final String APP_A = "CtsAppCloningTestApp.apk";
+    private static final String APP_A_PACKAGE = "com.android.cts.appcloningtestapp";
+
+    private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+    private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
+
+    private String mCloneUserId;
+    private ContentProviderHandler mContentProviderHandler;
+
+    @Before
+    public void setup() throws Exception {
+        assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
+        assumeTrue(isAtLeastS());
+        assumeFalse("Device uses sdcardfs", usesSdcardFs());
+
+        // create clone user
+        String output = executeShellCommand(
+                "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
+                        + "testUser");
+        mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+                "");
+        assertThat(mCloneUserId).isNotEmpty();
+
+        CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
+        assertThat(out.getStderr()).isEmpty();
+
+        mContentProviderHandler = new ContentProviderHandler(getDevice());
+        mContentProviderHandler.setUp();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (isHeadlessSystemUserMode() || !isAtLeastS() || usesSdcardFs()) return;
+        if (mContentProviderHandler != null) {
+            mContentProviderHandler.tearDown();
+        }
+
+        // remove the clone user
+        executeShellCommand("pm remove-user %s", mCloneUserId);
+    }
+
+    @Test
+    public void testInstallAppTwice() throws Exception {
+        installAppAsUser(APP_A, getCurrentUserId());
+        installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
+        uninstallPackage(APP_A_PACKAGE);
+    }
+
+    @Test
+    public void testCreateCloneUserFile() throws Exception {
+        CommandResult out;
+
+        // Check that the clone user directories exist
+        eventually(() -> {
+            // Wait for finish.
+            assertThat(isSuccessful(
+                    runContentProviderCommand("query", mCloneUserId,
+                            "/sdcard", ""))).isTrue();
+        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+
+        // Create a file on the clone user storage
+        out = executeShellV2Command("touch /sdcard/testFile.txt");
+        assertThat(isSuccessful(out)).isTrue();
+        eventually(() -> {
+            // Wait for finish.
+            assertThat(isSuccessful(
+                    runContentProviderCommand("write", mCloneUserId,
+                            "/sdcard/testFile.txt",
+                            "< /sdcard/testFile.txt"))).isTrue();
+        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+
+        // Check that the above created file exists on the clone user storage
+        out = runContentProviderCommand("query", mCloneUserId,
+                "/sdcard/testFile.txt", "");
+        assertThat(isSuccessful(out)).isTrue();
+
+        // Cleanup the created file
+        out = runContentProviderCommand("delete", mCloneUserId,
+                "/sdcard/testFile.txt", "");
+        assertThat(isSuccessful(out)).isTrue();
+    }
+
+    @Test
+    public void testPrivateAppDataDirectoryForCloneUser() throws Exception {
+        installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
+        eventually(() -> {
+            // Wait for finish.
+            assertThat(isPackageInstalled(APP_A_PACKAGE, mCloneUserId)).isTrue();
+        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+    }
+
+    private void installAppAsUser(String packageFile, int userId)
+            throws TargetSetupError, DeviceNotAvailableException {
+        installPackageAsUser(packageFile, false, userId, "-t");
+    }
+
+    private CommandResult runContentProviderCommand(String commandType, String userId,
+            String relativePath, String args) throws Exception {
+        String fullUri = CONTENT_PROVIDER_URL + relativePath;
+        return executeShellV2Command("content %s --user %s --uri %s %s",
+                commandType, userId, fullUri, args);
+    }
+
+    private boolean usesSdcardFs() throws Exception {
+        CommandResult out = executeShellV2Command("cat /proc/mounts");
+        assertThat(isSuccessful(out)).isTrue();
+        for (String line : out.getStdout().split("\n")) {
+            String[] split = line.split(" ");
+            if (split.length >= 3 && split[2].equals("sdcardfs")) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
new file mode 100644
index 0000000..3d301f1
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcloning;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+
+abstract class BaseHostTestCase extends BaseHostJUnit4Test {
+    private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
+    private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+    protected String executeShellCommand(String cmd, Object... args) throws Exception {
+        return getDevice().executeShellCommand(String.format(cmd, args));
+    }
+
+    protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception {
+        return getDevice().executeShellV2Command(String.format(cmd, args));
+    }
+
+    protected boolean isPackageInstalled(String packageName, String userId) throws Exception {
+        return getDevice().isPackageInstalled(packageName, userId);
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    protected boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+        String result = getDevice()
+                .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+        return "true".equalsIgnoreCase(result);
+    }
+
+    protected boolean isAtLeastS() throws DeviceNotAvailableException {
+        return getDevice().getApiLevel() >= 31 /* BUILD.VERSION_CODES.S */;
+    }
+
+    protected static void eventually(ThrowingRunnable r, long timeoutMillis) {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                r.run();
+                return;
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < timeoutMillis) {
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException ignored) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    protected int getCurrentUserId() throws Exception {
+        setCurrentUserId();
+
+        return mCurrentUserId;
+    }
+
+    protected boolean isSuccessful(CommandResult result) {
+        if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+            return false;
+        }
+        String stdout = result.getStdout();
+        if (stdout.contains(ERROR_MESSAGE_TAG)) {
+            return false;
+        }
+        String stderr = result.getStderr();
+        return (stderr == null || stderr.trim().isEmpty());
+    }
+
+    private void setCurrentUserId() throws Exception {
+        if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
+
+        ITestDevice device = getDevice();
+        mCurrentUserId = device.getCurrentUser();
+        CLog.i("Current user: %d");
+    }
+
+    protected interface ThrowingRunnable {
+        /**
+         * Similar to {@link Runnable#run} but has {@code throws Exception}.
+         */
+        void run() throws Exception;
+    }
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
new file mode 100644
index 0000000..b3edfec
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAppCloningTestApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "cts-install-lib",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    target_sdk_version: "current",
+    min_sdk_version: "30",
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..07d78a0
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appcloningtestapp"
+          android:versionCode="1"
+          android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="30" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appcloningtestapp" />
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
new file mode 100644
index 0000000..46480f7
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.appcloningtestapp;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AppCloningDeviceTest {
+    private static final String TAG = "AppCloningDeviceTest";
+}
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
index 3212c99..cd852ea 100644
--- a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/compatchanges/CompatChangesTest.java
@@ -17,6 +17,7 @@
 package com.android.cts.appcompat.compatchanges;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertThrows;
 
 import android.Manifest;
@@ -34,6 +35,9 @@
 import org.junit.runner.RunWith;
 
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
@@ -46,160 +50,199 @@
  */
 @RunWith(AndroidJUnit4.class)
 public final class CompatChangesTest {
-  private static final long CTS_SYSTEM_API_CHANGEID = 149391281L;
-  private static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039L;
-  private static final long UNKNOWN_CHANGEID = 123L;
+    private static final long CTS_SYSTEM_API_CHANGEID = 149391281L;
+    private static final long CTS_SYSTEM_API_OVERRIDABLE_CHANGEID = 174043039L;
+    private static final long UNKNOWN_CHANGEID = 123L;
 
-  private static final String OVERRIDE_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+    private static final String OVERRIDE_PACKAGE = "com.android.cts.appcompat.preinstalloverride";
+    private static final String OVERRIDE_PACKAGE2 = "com.android.cts.appcompat.preinstalloverride2";
 
-  @Before
-  public void setUp() {
-    InstrumentationRegistry.getInstrumentation().getUiAutomation()
-      .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
-                                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
-                                    Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD,
-                                    Manifest.permission.INTERACT_ACROSS_USERS_FULL);
-  }
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                        Manifest.permission.READ_COMPAT_CHANGE_CONFIG,
+                        Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD,
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+    }
 
-  @After
-  public void tearDown() {
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-  }
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
-  @Test
-  public void isChangeEnabled_changeEnabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+    @Test
+    public void isChangeEnabled_changeEnabled() {
+        assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
-  @Test
-  public void isChangeEnabledPackageName_changeEnabled() {
-    Context context = InstrumentationRegistry.getTargetContext();
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
-            context.getUser())).isTrue();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+    @Test
+    public void isChangeEnabledPackageName_changeEnabled() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+                context.getUser())).isTrue();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
-  @Test
-  public void isChangeEnabledUid_changeEnabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+    @Test
+    public void isChangeEnabledUid_changeEnabled() {
+        assertThat(
+                CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
-  @Test
-  public void isChangeEnabled_changeDisabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+    @Test
+    public void isChangeEnabled_changeDisabled() {
+        assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
-  @Test
-  public void isChangeEnabledPackageName_changeDisabled() {
-    Context context = InstrumentationRegistry.getTargetContext();
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
-            context.getUser())).isFalse();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+    @Test
+    public void isChangeEnabledPackageName_changeDisabled() {
+        Context context = InstrumentationRegistry.getTargetContext();
+        assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+                context.getUser())).isFalse();
+    }
 
-  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
-  @Test
-  public void isChangeEnabledUid_changeDisabled() {
-    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
-  }
+    /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+    @Test
+    public void isChangeEnabledUid_changeDisabled() {
+        assertThat(
+                CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+    }
 
-  @Test
-  public void putPackageOverrides_securityExceptionForNonOverridableChangeId() {
-    SecurityException e = assertThrows(SecurityException.class,
-            () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-                    Collections.singletonMap(CTS_SYSTEM_API_CHANGEID,
-                    new PackageOverride.Builder().setEnabled(true).build())));
-    assertThat(e).hasMessageThat().contains("marked as Overridable");
-  }
+    @Test
+    public void putPackageOverrides_securityExceptionForNonOverridableChangeId() {
+        SecurityException e = assertThrows(SecurityException.class,
+                () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                        Collections.singletonMap(CTS_SYSTEM_API_CHANGEID,
+                                new PackageOverride.Builder().setEnabled(true).build())));
+        assertThat(e).hasMessageThat().contains("marked as Overridable");
+    }
 
-  @Test
-  public void putPackageOverrides_doesNothingIfChangeIsUnknown() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(UNKNOWN_CHANGEID,
-                    new PackageOverride.Builder().setEnabled(true).build()));
-  }
+    @Test
+    public void putPackageOverrides_doesNothingIfChangeIsUnknown() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(UNKNOWN_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+    }
 
-  @Test
-  public void putPackageOverrides_success() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                    new PackageOverride.Builder().setEnabled(true).build()));
-  }
+    @Test
+    public void putPackageOverrides_success() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+    }
 
-  @Test
-  public void putPackageOverrides_fromVersion2() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                    new PackageOverride.Builder().setMinVersionCode(2).setEnabled(true).build()));
-  }
+    @Test
+    public void putPackageOverrides_fromVersion2() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setMinVersionCode(2).setEnabled(
+                                true).build()));
+    }
 
-  @Test
-  public void putPackageOverrides_untilVersion1() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                    new PackageOverride.Builder().setMaxVersionCode(1).setEnabled(true).build()));
-  }
+    @Test
+    public void putPackageOverrides_untilVersion1() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setMaxVersionCode(1).setEnabled(
+                                true).build()));
+    }
 
-  @Test
-  public void putPackageOverrides_securityExceptionForNotHoldingPermission() {
-    // Adopt the normal override permission that doesn't allow to clear overrides on release builds
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-            Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+    @Test
+    public void putPackageOverrides_securityExceptionForNotHoldingPermission() {
+        // Adopt the normal override permission that doesn't allow to clear overrides on release
+        // builds
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
 
-    SecurityException e = assertThrows(SecurityException.class,
-            () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-                    Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                            new PackageOverride.Builder().setEnabled(true).build())));
-    assertThat(e).hasMessageThat().contains("Cannot override compat change");
-  }
+        SecurityException e = assertThrows(SecurityException.class,
+                () -> CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                        Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                                new PackageOverride.Builder().setEnabled(true).build())));
+        assertThat(e).hasMessageThat().contains("Cannot override compat change");
+    }
 
-  @Test
-  public void removePackageOverrides_securityExceptionForNonOverridableChangeId() {
-    SecurityException e = assertThrows(SecurityException.class,
-            () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
-                    Collections.singleton(CTS_SYSTEM_API_CHANGEID)));
-    assertThat(e).hasMessageThat().contains("marked as Overridable");
-  }
+    @Test
+    public void putAllPackageOverrides_success() {
+        Map<String, Map<Long, PackageOverride>> packageNameToOverrides = new HashMap<>();
+        packageNameToOverrides.put(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+        packageNameToOverrides.put(OVERRIDE_PACKAGE2,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+        CompatChanges.putAllPackageOverrides(packageNameToOverrides);
+    }
 
-  @Test
-  public void removePackageOverrides_doesNothingIfOverrideNotPresent() {
-    CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
-  }
+    @Test
+    public void removePackageOverrides_securityExceptionForNonOverridableChangeId() {
+        SecurityException e = assertThrows(SecurityException.class,
+                () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+                        Collections.singleton(CTS_SYSTEM_API_CHANGEID)));
+        assertThat(e).hasMessageThat().contains("marked as Overridable");
+    }
 
-  @Test
-  public void removePackageOverrides_doesNothingIfChangeIsUnknown() {
-    CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singleton(UNKNOWN_CHANGEID));
-  }
+    @Test
+    public void removePackageOverrides_doesNothingIfOverrideNotPresent() {
+        CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+    }
 
-  @Test
-  public void removePackageOverrides_overridePresentSuccess() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                    new PackageOverride.Builder().setEnabled(true).build()));
-    CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
-  }
+    @Test
+    public void removePackageOverrides_doesNothingIfChangeIsUnknown() {
+        CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singleton(UNKNOWN_CHANGEID));
+    }
 
-  @Test
-  public void removePackageOverrides_securityExceptionForNotHoldingPermission() {
-    CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
-            Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
-                    new PackageOverride.Builder().setEnabled(true).build()));
+    @Test
+    public void removePackageOverrides_overridePresentSuccess() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+        CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+    }
 
-    // Adopt the normal override permission that doesn't allow to clear overrides on release builds
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
-    InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
-            Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+    @Test
+    public void removePackageOverrides_securityExceptionForNotHoldingPermission() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
 
-    SecurityException e = assertThrows(SecurityException.class,
-            () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
-                    Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID)));
-    assertThat(e).hasMessageThat().contains("Cannot override compat change");
-  }
+        // Adopt the normal override permission that doesn't allow to clear overrides on release
+        // builds
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.OVERRIDE_COMPAT_CHANGE_CONFIG);
+
+        SecurityException e = assertThrows(SecurityException.class,
+                () -> CompatChanges.removePackageOverrides(OVERRIDE_PACKAGE,
+                        Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID)));
+        assertThat(e).hasMessageThat().contains("Cannot override compat change");
+    }
+
+    @Test
+    public void removeAllPackageOverrides_overridePresentSuccess() {
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+        CompatChanges.putPackageOverrides(OVERRIDE_PACKAGE2,
+                Collections.singletonMap(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID,
+                        new PackageOverride.Builder().setEnabled(true).build()));
+
+        Map<String, Set<Long>> packageNameToOverridesToRemove = new HashMap<>();
+        packageNameToOverridesToRemove.put(OVERRIDE_PACKAGE,
+                Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+        packageNameToOverridesToRemove.put(OVERRIDE_PACKAGE2,
+                Collections.singleton(CTS_SYSTEM_API_OVERRIDABLE_CHANGEID));
+        CompatChanges.removeAllPackageOverrides(packageNameToOverridesToRemove);
+    }
 }
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
index 0353cb7..9d2b53e 100644
--- a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/Android.bp
@@ -79,3 +79,17 @@
         "general-tests",
     ],
 }
+
+android_test_helper_app {
+    name: "appcompat_preinstall_override2_versioncode1_release",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    static_libs: ["pre_install_override_lib"],
+    manifest: "AndroidManifest_2_versioncode1_release.xml",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.xml b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.xml
new file mode 100644
index 0000000..15a89c7
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/AndroidManifest_2_versioncode1_release.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="com.android.cts.appcompat.preinstalloverride2"
+          android:versionCode="1">
+    <uses-sdk android:targetSdkVersion="30"/>
+    <application android:debuggable="false">
+        <activity android:name=".Empty" android:exported="true" />
+    </application>
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.java b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.java
new file mode 100644
index 0000000..fd8061f
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/preInstallOverrideApps/src/com/android/cts/appcompat/preinstalloverride2/EmptyActivity.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.cts.appcompat.preinstalloverride2;
+
+import android.app.Activity;
+
+public class EmptyActivity extends Activity {
+
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
index 66fdf19..fbc7a58 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesOverrideOnReleaseBuildTest.java
@@ -29,6 +29,7 @@
     private static final String TEST_PKG = "com.android.cts.appcompat.compatchanges";
 
     private static final String OVERRIDE_PKG = "com.android.cts.appcompat.preinstalloverride";
+    private static final String OVERRIDE_PKG2 = "com.android.cts.appcompat.preinstalloverride2";
 
     private static final long CTS_OVERRIDABLE_CHANGE_ID = 174043039L;
     private static final long UNKNOWN_CHANGEID = 123L;
@@ -37,14 +38,17 @@
     protected void setUp() throws Exception {
         installPackage(TEST_APK, true);
         runCommand("am compat reset-all " + OVERRIDE_PKG);
+        runCommand("am compat reset-all " + OVERRIDE_PKG2);
         runCommand("settings put global force_non_debuggable_final_build_for_compat 1");
     }
 
     @Override
     protected void tearDown() throws Exception {
         runCommand("am compat reset-all " + OVERRIDE_PKG);
+        runCommand("am compat reset-all " + OVERRIDE_PKG2);
         uninstallPackage(TEST_PKG, true);
         uninstallPackage(OVERRIDE_PKG, false);
+        uninstallPackage(OVERRIDE_PKG2, false);
         runCommand("settings put global force_non_debuggable_final_build_for_compat 0");
     }
 
@@ -176,6 +180,26 @@
         assertThat(ctsChange.hasOverrides).isFalse();
     }
 
+    public void testPutAllPackageOverrides() throws Exception {
+        installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+        installPackage("appcompat_preinstall_override2_versioncode1_release.apk", false);
+
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+                "putAllPackageOverrides_success",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of());
+
+        Change ctsChange = getOnDeviceChangeIdConfig(CTS_OVERRIDABLE_CHANGE_ID);
+        assertWithMessage("CTS specific change %s not found on device", CTS_OVERRIDABLE_CHANGE_ID)
+                .that(ctsChange).isNotNull();
+        assertThat(ctsChange.hasRawOverrides).isTrue();
+        assertThat(ctsChange.rawOverrideStr).isEqualTo(
+                "{" + OVERRIDE_PKG + "=true, " + OVERRIDE_PKG2 + "=true}");
+        assertThat(ctsChange.hasOverrides).isTrue();
+        assertThat(ctsChange.overridesStr).isEqualTo(
+                "{" + OVERRIDE_PKG + "=true, " + OVERRIDE_PKG2 + "=true}");
+    }
+
     public void testRemovePackageOverridesSecurityExceptionNonOverridableChangeId()
             throws Exception {
         installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
@@ -243,4 +267,20 @@
         assertThat(ctsChange.hasRawOverrides).isFalse();
         assertThat(ctsChange.hasOverrides).isFalse();
     }
+
+    public void testRemoveAllPackageOverridesWhenOverridePresent() throws Exception {
+        installPackage("appcompat_preinstall_override_versioncode1_release.apk", false);
+        installPackage("appcompat_preinstall_override2_versioncode1_release.apk", false);
+
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+                "removeAllPackageOverrides_overridePresentSuccess",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of());
+
+        Change ctsChange = getOnDeviceChangeIdConfig(CTS_OVERRIDABLE_CHANGE_ID);
+        assertWithMessage("CTS specific change %s not found on device", CTS_OVERRIDABLE_CHANGE_ID)
+                .that(ctsChange).isNotNull();
+        assertThat(ctsChange.hasRawOverrides).isFalse();
+        assertThat(ctsChange.hasOverrides).isFalse();
+    }
 }
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
index 4b7edc7..aa87d2a 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSelinuxTest.java
@@ -147,8 +147,7 @@
     }
 
     private void startApp() throws Exception {
-        runCommand("am start -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
-        Thread.currentThread().sleep(5000);
+        runCommand("am start-activity -W -n " + TEST_PKG + "/" + TEST_PKG + ".Empty");
     }
 
 
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
index ef1c220..ad827f2 100644
--- a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesValidConfigTest.java
@@ -24,6 +24,10 @@
 
 import com.google.common.collect.ImmutableSet;
 
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
@@ -31,10 +35,6 @@
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-import org.w3c.dom.NodeList;
-
 public final class CompatChangesValidConfigTest extends CompatChangeGatingTestCase {
 
     private static final Set<String> OVERRIDES_ALLOWLIST = ImmutableSet.of(
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index fcb20e8..3c84b1c 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -21,7 +21,6 @@
     defaults: ["cts_defaults"],
     srcs: ["src/**/*.java"],
     libs: [
-        "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
     ],
@@ -35,4 +34,6 @@
         "general-tests",
         "mts",
     ],
+    data: [":SharedLibraryInfoTestApp"],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index 04e12f9..b3e2cab 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -24,29 +24,34 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.compat.testing.Classpaths;
+import android.compat.testing.SharedLibraryInfo;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 
 import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Stream;
+
 
 /**
  * Tests for detecting no duplicate class files are present on BOOTCLASSPATH and
@@ -58,6 +63,16 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StrictJavaPackagesTest extends BaseHostJUnit4Test {
 
+    private static final String ANDROID_TEST_MOCK_JAR = "/system/framework/android.test.mock.jar";
+
+    private static ImmutableList<String> sBootclasspathJars;
+    private static ImmutableList<String> sSystemserverclasspathJars;
+    private static ImmutableList<String> sSharedLibJars;
+    private static ImmutableList<SharedLibraryInfo> sSharedLibs;
+    private static ImmutableSetMultimap<String, String> sJarsToClasses;
+
+    private DeviceSdkLevel mDeviceSdkLevel;
+
     /**
      * This is the list of classes that are currently duplicated and should be addressed.
      *
@@ -201,33 +216,168 @@
     private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
 
     private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST =
-        ImmutableSet.of(
-            "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
-            "Landroid/hidl/base/V1_0/IBase;",
-            "Landroid/hidl/base/V1_0/IBase$Proxy;",
-            "Landroid/hidl/base/V1_0/IBase$Stub;",
-            "Landroid/hidl/base/V1_0/DebugInfo;",
-            "Landroid/hidl/safe_union/V1_0/Monostate;"
-        );
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/base/V1_0/IBase$Proxy;",
+                    "Landroid/hidl/base/V1_0/IBase$Stub;",
+                    "Landroid/hidl/base/V1_0/DebugInfo;",
+                    "Landroid/hidl/safe_union/V1_0/Monostate;"
+            );
 
     private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST =
-        ImmutableSet.of(
-            "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
-            "Landroid/hidl/base/V1_0/IBase;",
-            "Landroid/hidl/base/V1_0/IBase$Proxy;",
-            "Landroid/hidl/base/V1_0/IBase$Stub;",
-            "Landroid/hidl/base/V1_0/DebugInfo;"
-        );
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/base/V1_0/IBase$Proxy;",
+                    "Landroid/hidl/base/V1_0/IBase$Stub;",
+                    "Landroid/hidl/base/V1_0/DebugInfo;"
+            );
+
+    /**
+     * TODO(b/199529199): Address these.
+     * List of duplicate classes between bootclasspath and shared libraries.
+     *
+     * <p> DO NOT ADD CLASSES TO THIS LIST!
+     */
+    private static final Set<String> BCP_AND_SHARED_LIB_BURNDOWN_LIST =
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/manager/V1_0/IServiceManager;",
+                    "Landroid/hidl/manager/V1_0/IServiceNotification;",
+                    "Landroidx/annotation/Keep;",
+                    "Lcom/google/android/embms/nano/EmbmsProtos;",
+                    "Lcom/google/protobuf/nano/android/ParcelableExtendableMessageNano;",
+                    "Lcom/google/protobuf/nano/android/ParcelableMessageNano;",
+                    "Lcom/google/protobuf/nano/android/ParcelableMessageNanoCreator;",
+                    "Lcom/google/protobuf/nano/CodedInputByteBufferNano;",
+                    "Lcom/google/protobuf/nano/CodedOutputByteBufferNano;",
+                    "Lcom/google/protobuf/nano/ExtendableMessageNano;",
+                    "Lcom/google/protobuf/nano/Extension;",
+                    "Lcom/google/protobuf/nano/FieldArray;",
+                    "Lcom/google/protobuf/nano/FieldData;",
+                    "Lcom/google/protobuf/nano/InternalNano;",
+                    "Lcom/google/protobuf/nano/InvalidProtocolBufferNanoException;",
+                    "Lcom/google/protobuf/nano/MapFactories;",
+                    "Lcom/google/protobuf/nano/MessageNano;",
+                    "Lcom/google/protobuf/nano/MessageNanoPrinter;",
+                    "Lcom/google/protobuf/nano/UnknownFieldData;",
+                    "Lcom/google/protobuf/nano/WireFormatNano;",
+                    "Lcom/qualcomm/qcrilhook/BaseQmiTypes;",
+                    "Lcom/qualcomm/qcrilhook/CSignalStrength;",
+                    "Lcom/qualcomm/qcrilhook/EmbmsOemHook;",
+                    "Lcom/qualcomm/qcrilhook/EmbmsProtoUtils;",
+                    "Lcom/qualcomm/qcrilhook/IOemHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/IQcRilHook;",
+                    "Lcom/qualcomm/qcrilhook/IQcRilHookExt;",
+                    "Lcom/qualcomm/qcrilhook/OemHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/PresenceMsgBuilder;",
+                    "Lcom/qualcomm/qcrilhook/PresenceMsgParser;",
+                    "Lcom/qualcomm/qcrilhook/PresenceOemHook;",
+                    "Lcom/qualcomm/qcrilhook/PrimitiveParser;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHook;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookCallbackExt;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookExt;",
+                    "Lcom/qualcomm/qcrilhook/QmiOemHook;",
+                    "Lcom/qualcomm/qcrilhook/QmiOemHookConstants;",
+                    "Lcom/qualcomm/qcrilhook/QmiPrimitiveTypes;",
+                    "Lcom/qualcomm/qcrilhook/TunerOemHook;",
+                    "Lcom/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel;",
+                    "Lcom/qualcomm/utils/CommandException;",
+                    "Lcom/qualcomm/utils/RILConstants;",
+                    "Lorg/codeaurora/telephony/utils/CommandException;",
+                    "Lorg/codeaurora/telephony/utils/Log;",
+                    "Lorg/codeaurora/telephony/utils/RILConstants;",
+                    "Lorg/chromium/net/ApiVersion;",
+                    "Lorg/chromium/net/BidirectionalStream;",
+                    "Lorg/chromium/net/CallbackException;",
+                    "Lorg/chromium/net/CronetEngine;",
+                    "Lorg/chromium/net/CronetException;",
+                    "Lorg/chromium/net/CronetProvider;",
+                    "Lorg/chromium/net/EffectiveConnectionType;",
+                    "Lorg/chromium/net/ExperimentalBidirectionalStream;",
+                    "Lorg/chromium/net/ExperimentalCronetEngine;",
+                    "Lorg/chromium/net/ExperimentalUrlRequest;",
+                    "Lorg/chromium/net/ICronetEngineBuilder;",
+                    "Lorg/chromium/net/InlineExecutionProhibitedException;",
+                    "Lorg/chromium/net/NetworkException;",
+                    "Lorg/chromium/net/NetworkQualityRttListener;",
+                    "Lorg/chromium/net/NetworkQualityThroughputListener;",
+                    "Lorg/chromium/net/QuicException;",
+                    "Lorg/chromium/net/RequestFinishedInfo;",
+                    "Lorg/chromium/net/RttThroughputValues;",
+                    "Lorg/chromium/net/ThreadStatsUid;",
+                    "Lorg/chromium/net/UploadDataProvider;",
+                    "Lorg/chromium/net/UploadDataProviders;",
+                    "Lorg/chromium/net/UploadDataSink;",
+                    "Lorg/chromium/net/UrlRequest;",
+                    "Lorg/chromium/net/UrlResponseInfo;"
+            );
+
+    /**
+     * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes.
+     *
+     * <p>This method cannot be static, as there are no static equivalents for {@link #getDevice()}
+     * and {@link #getBuild()}.
+     */
+    @BeforeClassWithInfo
+    public static void setupOnce(TestInformation testInfo) throws Exception {
+        if (testInfo.getDevice() == null || testInfo.getBuildInfo() == null) {
+            throw new RuntimeException("No device and/or build type specified!");
+        }
+        DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(testInfo.getDevice());
+
+        sBootclasspathJars = Classpaths.getJarsOnClasspath(testInfo.getDevice(), BOOTCLASSPATH);
+        sSystemserverclasspathJars =
+                Classpaths.getJarsOnClasspath(testInfo.getDevice(), SYSTEMSERVERCLASSPATH);
+        sSharedLibs = deviceSdkLevel.isDeviceAtLeastS()
+                ? Classpaths.getSharedLibraryInfos(testInfo.getDevice(), testInfo.getBuildInfo())
+                : ImmutableList.of();
+        sSharedLibJars = sSharedLibs.stream()
+                .map(sharedLibraryInfo -> sharedLibraryInfo.paths)
+                .flatMap(ImmutableCollection::stream)
+                .filter(file -> doesFileExist(file, testInfo.getDevice()))
+                .collect(ImmutableList.toImmutableList());
+
+        final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
+                ImmutableSetMultimap.builder();
+        Stream.of(sBootclasspathJars.stream(),
+                        sSystemserverclasspathJars.stream(),
+                        sSharedLibJars.stream())
+                .reduce(Stream::concat).orElseGet(Stream::empty)
+                .parallel()
+                .forEach(jar -> {
+                    try {
+                        ImmutableSet<String> classes =
+                                Classpaths.getClassDefsFromJar(testInfo.getDevice(), jar).stream()
+                                        .map(ClassDef::getType)
+                                        // Inner classes always go with their parent.
+                                        .filter(className -> !className.contains("$"))
+                                        .collect(ImmutableSet.toImmutableSet());
+                        synchronized (jarsToClasses) {
+                            jarsToClasses.putAll(jar, classes);
+                        }
+                    } catch (DeviceNotAvailableException | IOException e) {
+                        throw new RuntimeException(e);
+                    }
+                });
+        sJarsToClasses = jarsToClasses.build();
+    }
+
+    @Before
+    public void setup() {
+        mDeviceSdkLevel = new DeviceSdkLevel(getDevice());
+    }
 
     /**
      * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
      */
     @Test
     public void testBootclasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-        assertThat(getDuplicateClasses(jars)).isEmpty();
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+        assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty();
     }
 
     /**
@@ -235,9 +385,7 @@
      */
     @Test
     public void testSystemServerClasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
         ImmutableSet<String> overlapBurndownList;
         if (hasFeature(FEATURE_AUTOMOTIVE)) {
             overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST);
@@ -246,7 +394,7 @@
         } else {
             overlapBurndownList = ImmutableSet.of();
         }
-        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
         Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
                 duplicate -> !overlapBurndownList.contains(duplicate));
 
@@ -259,10 +407,10 @@
      */
     @Test
     public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
         ImmutableList.Builder<String> jars = ImmutableList.builder();
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSystemserverclasspathJars);
         ImmutableSet<String> overlapBurndownList;
         if (hasFeature(FEATURE_AUTOMOTIVE)) {
             overlapBurndownList = ImmutableSet.<String>builder()
@@ -287,13 +435,9 @@
      */
     @Test
     public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception {
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-
-        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> duplicates = getDuplicateClasses(sBootclasspathJars);
         Multimap<String, String> filtered =
                 Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
-
         assertThat(filtered).isEmpty();
     }
 
@@ -302,10 +446,7 @@
      */
     @Test
     public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception {
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-
-        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
         Multimap<String, String> filtered =
                 Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
 
@@ -320,8 +461,8 @@
     public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses()
             throws Exception {
         ImmutableList.Builder<String> jars = ImmutableList.builder();
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSystemserverclasspathJars);
 
         Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
         Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
@@ -332,34 +473,89 @@
     }
 
     /**
+     * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and
+     * shared library jars.
+     */
+    @Test
+    public void testBootClasspathAndSharedLibs_nonDuplicateClasses() throws Exception {
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastS());
+        final ImmutableList.Builder<String> jars = ImmutableList.builder();
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSharedLibJars);
+        final Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
+        final Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+                dupeClass -> {
+                    try {
+                        final Collection<String> dupeJars = duplicates.get(dupeClass);
+                        // Duplicate is already known.
+                        if (BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(dupeClass)) {
+                            return false;
+                        }
+                        // Duplicate is only between different versions of the same shared library.
+                        if (isSameLibrary(dupeJars)) {
+                            return false;
+                        }
+                        // Pre-T, the Android test mock library included some platform classes.
+                        if (!mDeviceSdkLevel.isDeviceAtLeastT()
+                                && dupeJars.contains(ANDROID_TEST_MOCK_JAR)) {
+                            return false;
+                        }
+                        // Different versions of the same library may have different names, and
+                        // there's
+                        // no reliable way to dedupe them. Ignore duplicates if they do not
+                        // include apex jars.
+                        if (dupeJars.stream().noneMatch(lib -> lib.startsWith("/apex/"))) {
+                            return false;
+                        }
+                    } catch (DeviceNotAvailableException e) {
+                        throw new RuntimeException(e);
+                    }
+                    return true;
+                });
+        assertThat(filtered).isEmpty();
+    }
+
+    /**
      * Gets the duplicate classes within a list of jar files.
      *
      * @param jars a list of jar files.
      * @return a multimap with the class name as a key and the jar files as a value.
      */
-    private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars)
-            throws Exception {
-        final Multimap<String, String> allClasses = HashMultimap.create();
-        for (String jar : jars) {
-            ImmutableSet<ClassDef> classes = Classpaths.getClassDefsFromJar(getDevice(), jar);
-            for (ClassDef classDef : classes) {
-                // No need to worry about inner classes, as they always go with their parent.
-                if (!classDef.getType().contains("$")) {
-                    allClasses.put(classDef.getType(), jar);
-                }
-            }
-        }
+    private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) {
+        final HashMultimap<String, String> allClasses = HashMultimap.create();
+        Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses);
+        return Multimaps.filterKeys(allClasses, key -> allClasses.get(key).size() > 1);
+    }
 
-        final Multimap<String, String> duplicates = HashMultimap.create();
-        for (String clazz : allClasses.keySet()) {
-            Collection<String> jarsWithClazz = allClasses.get(clazz);
-            if (jarsWithClazz.size() > 1) {
-                CLog.i("Class %s is duplicated in %s", clazz, jarsWithClazz);
-                duplicates.putAll(clazz, jarsWithClazz);
-            }
+    private static boolean doesFileExist(String path, ITestDevice device) {
+        assertThat(path).isNotNull();
+        try {
+            return device.doesFileExist(path);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException("Could not check whether " + path + " exists on device", e);
         }
+    }
 
-        return duplicates;
+    /**
+     * Get the name of a shared library.
+     *
+     * @return the shared library name or the jar's path if it's not a shared library.
+     */
+    private String getSharedLibraryNameOrPath(String jar) {
+        return sSharedLibs.stream()
+                .filter(sharedLib -> sharedLib.paths.contains(jar))
+                .map(sharedLib -> sharedLib.name)
+                .findFirst().orElse(jar);
+    }
+
+    /**
+     * Check whether a list of jars are all different versions of the same library.
+     */
+    private boolean isSameLibrary(Collection<String> jars) {
+        return jars.stream()
+                .map(this::getSharedLibraryNameOrPath)
+                .distinct()
+                .count() == 1;
     }
 
     private boolean hasFeature(String featureName) throws DeviceNotAvailableException {
diff --git a/hostsidetests/appsearch/Android.bp b/hostsidetests/appsearch/Android.bp
index 57a33b8..36a27d7 100644
--- a/hostsidetests/appsearch/Android.bp
+++ b/hostsidetests/appsearch/Android.bp
@@ -29,11 +29,12 @@
         "tools-common-prebuilt",
         "cts-tradefed",
         "tradefed",
-        "truth-prebuilt"
+        "truth-prebuilt",
     ],
     test_suites: [
         "cts",
         "general-tests",
+        "mts-appsearch",
     ],
 }
 
@@ -53,6 +54,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-appsearch",
     ],
     manifest: "test-apps/AppSearchHostTestHelperA/AndroidManifest.xml",
     certificate: ":cts-appsearch-hosttest-helper-cert-a",
@@ -75,6 +77,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-appsearch",
     ],
     manifest: "test-apps/AppSearchHostTestHelperB/AndroidManifest.xml",
     certificate: ":cts-appsearch-hosttest-helper-cert-b",
diff --git a/hostsidetests/appsearch/AndroidTest.xml b/hostsidetests/appsearch/AndroidTest.xml
index 229e352..a538916 100644
--- a/hostsidetests/appsearch/AndroidTest.xml
+++ b/hostsidetests/appsearch/AndroidTest.xml
@@ -28,4 +28,8 @@
         <option name="test-file-name" value="CtsAppSearchHostTestHelperB.apk" />
         <option name="cleanup-apks" value="true" />
     </target_preparer>
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.appsearch" />
+    </object>
 </configuration>
diff --git a/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java b/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
index 33ccf39..eb0ef3c 100644
--- a/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
+++ b/hostsidetests/appsearch/src/android/appsearch/cts/AppSearchMultiUserTest.java
@@ -20,9 +20,11 @@
 
 import static org.junit.Assume.assumeTrue;
 
+import com.android.tradefed.invoker.TestInformation;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -45,79 +47,100 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class AppSearchMultiUserTest extends AppSearchHostTestBase {
 
-    private int mInitialUserId;
-    private int mSecondaryUserId;
+    private static int sInitialUserId;
+    private static int sSecondaryUserId;
+
+    @BeforeClassWithInfo
+    public static void setUpClass(TestInformation testInfo) throws Exception {
+        assumeTrue("Multi-user is not supported on this device",
+                testInfo.getDevice().isMultiUserSupported());
+
+        sInitialUserId = testInfo.getDevice().getPrimaryUserId();
+        sSecondaryUserId = testInfo.getDevice().createUser("Test_User");
+        assertThat(testInfo.getDevice().startUser(sSecondaryUserId)).isTrue();
+    }
 
     @Before
     public void setUp() throws Exception {
-        assumeTrue("Multi-user is not supported on this device",
-                getDevice().isMultiUserSupported());
+        if (!getDevice().isUserRunning(sSecondaryUserId)) {
+            getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+        }
+        installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, sInitialUserId);
+        installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, sSecondaryUserId);
 
-        mInitialUserId = getDevice().getCurrentUser();
-        mSecondaryUserId = getDevice().createUser("Test_User");
-        assertThat(getDevice().startUser(mSecondaryUserId)).isTrue();
-
-        installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, mInitialUserId);
-        installPackageAsUser(TARGET_APK_A, /* grantPermission= */true, mSecondaryUserId);
-
-        runDeviceTestAsUserInPkgA("clearTestData", mInitialUserId);
-        runDeviceTestAsUserInPkgA("clearTestData", mSecondaryUserId);
+        runDeviceTestAsUserInPkgA("clearTestData", sInitialUserId);
+        runDeviceTestAsUserInPkgA("clearTestData", sSecondaryUserId);
     }
 
-    @After
-    public void tearDown() throws Exception {
-        if (getDevice().getInstalledPackageNames().contains(TARGET_PKG_A)) {
-            runDeviceTestAsUserInPkgA("clearTestData", mInitialUserId);
-        }
-        if (mSecondaryUserId > 0) {
-            getDevice().removeUser(mSecondaryUserId);
+    @AfterClassWithInfo
+    public static void tearDownClass(TestInformation testInfo) throws Exception {
+        if (sSecondaryUserId > 0) {
+            testInfo.getDevice().removeUser(sSecondaryUserId);
         }
     }
 
     @Test
-    public void testMultiUser_documentAccess() throws Exception {
-        runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
-        runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
+    public void testMultiUser_cantAccessOtherUsersData() throws Exception {
+        runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
+        runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
         // Cannot get the document from another user.
-        runDeviceTestAsUserInPkgA("testGetDocuments_nonexist", mInitialUserId);
+        runDeviceTestAsUserInPkgA("testGetDocuments_nonexist", sInitialUserId);
+    }
+
+    @Test
+    public void testMultiUser_canInteractAsAnotherUser() throws Exception {
+        Map<String, String> args =
+                Collections.singletonMap(USER_ID_KEY, String.valueOf(sSecondaryUserId));
+
+        // We can do the normal set of operations while pretending to be another user.
+        runDeviceTestAsUserInPkgA("testPutDocumentsAsAnotherUser", sInitialUserId, args);
+        runDeviceTestAsUserInPkgA("testGetDocumentsAsAnotherUser_exist", sInitialUserId, args);
+    }
+
+    @Test
+    public void testCreateSessionInStoppedUser() throws Exception {
+        Map<String, String> args =
+                Collections.singletonMap(USER_ID_KEY, String.valueOf(sSecondaryUserId));
+        getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+        runDeviceTestAsUserInPkgA("createSessionInStoppedUser", sInitialUserId, args);
     }
 
     @Test
     public void testStopUser_persistData() throws Exception {
-        runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
-        runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
-        getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
-        getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
-        runDeviceTestAsUserInPkgA("testGetDocuments_exist", mSecondaryUserId);
+        runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
+        runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
+        getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+        getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+        runDeviceTestAsUserInPkgA("testGetDocuments_exist", sSecondaryUserId);
     }
 
     @Test
     public void testPackageUninstall_onLockedUser() throws Exception {
-        installPackageAsUser(TARGET_APK_B, /* grantPermission= */true, mSecondaryUserId);
+        installPackageAsUser(TARGET_APK_B, /* grantPermission= */true, sSecondaryUserId);
         // package A grants visibility to package B.
-        runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
+        runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
         // query the document from another package.
-        runDeviceTestAsUserInPkgB("testGlobalGetDocuments_exist", mSecondaryUserId);
-        getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+        runDeviceTestAsUserInPkgB("testGlobalGetDocuments_exist", sSecondaryUserId);
+        getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
         uninstallPackage(TARGET_PKG_A);
-        getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
-        runDeviceTestAsUserInPkgB("testGlobalGetDocuments_nonexist", mSecondaryUserId);
+        getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
+        runDeviceTestAsUserInPkgB("testGlobalGetDocuments_nonexist", sSecondaryUserId);
     }
 
     @Test
     public void testReadStorageInfoFromFile() throws Exception {
-        runDeviceTestAsUserInPkgA("testPutDocuments", mSecondaryUserId);
+        runDeviceTestAsUserInPkgA("testPutDocuments", sSecondaryUserId);
 
-        getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
-        getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
+        getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+        getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
         // Write user's storage info into file while initializing AppSearchImpl.
-        runStorageAugmenterDeviceTestAsUserInPkgA("connectToAppSearch", mSecondaryUserId);
+        runStorageAugmenterDeviceTestAsUserInPkgA("connectToAppSearch", sSecondaryUserId);
 
         // Disconnect user from AppSearch. While AppSearchImpl doesn't exist for the user, user's
         // storage info would be read from file.
-        getDevice().stopUser(mSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
-        getDevice().startUser(mSecondaryUserId, /*waitFlag=*/true);
+        getDevice().stopUser(sSecondaryUserId, /*waitFlag=*/true, /*forceFlag=*/true);
+        getDevice().startUser(sSecondaryUserId, /*waitFlag=*/true);
 
-        runStorageAugmenterDeviceTestAsUserInPkgA("testReadStorageInfo", mSecondaryUserId);
+        runStorageAugmenterDeviceTestAsUserInPkgA("testReadStorageInfo", sSecondaryUserId);
     }
 }
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
index b1f2293..b5ec0396 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchDeviceTest.java
@@ -16,13 +16,14 @@
 
 package android.appsearch.app.a;
 
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.testng.Assert.expectThrows;
 
+import android.Manifest;
 import android.app.UiAutomation;
 import android.app.appsearch.AppSearchBatchResult;
 import android.app.appsearch.AppSearchManager;
@@ -34,13 +35,12 @@
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 import android.os.Bundle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import com.google.common.io.BaseEncoding;
 
 import org.junit.Before;
@@ -109,12 +109,60 @@
     }
 
     @Test
+    public void testPutDocumentsAsAnotherUser() throws Exception {
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            Bundle args = InstrumentationRegistry.getArguments();
+            int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+
+            // Initialize as other user
+            AppSearchSessionShim db = AppSearchSessionShimImpl.createSearchSession(
+                    new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+                    userId).get();
+
+            // Schema registration
+            db.setSchema(new SetSchemaRequest.Builder().addSchemas(SCHEMA)
+                    .setSchemaTypeVisibilityForPackage(SCHEMA.getSchemaType(), /*visible=*/ true,
+                            new PackageIdentifier(PKG_B, PKG_B_CERT_SHA256)).build()).get();
+
+            // Index a document
+            AppSearchBatchResult<String, Void> result = checkIsBatchResultSuccess(
+                    db.put(new PutDocumentsRequest.Builder().addGenericDocuments(DOCUMENT).build()));
+            assertThat(result.getSuccesses()).containsExactly(ID, /*v0=*/null);
+            assertThat(result.getFailures()).isEmpty();
+
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testGetDocuments_exist() throws Exception {
         List<GenericDocument> outDocuments = doGet(mDb, NAMESPACE, ID);
         assertThat(outDocuments).containsExactly(DOCUMENT);
     }
 
     @Test
+    public void testGetDocumentsAsAnotherUser_exist() throws Exception {
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            Bundle args = InstrumentationRegistry.getArguments();
+            int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+
+            // Initialize as other user
+            AppSearchSessionShim db = AppSearchSessionShimImpl.createSearchSession(
+                    new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+                    userId).get();
+
+            // Get documents
+            List<GenericDocument> outDocuments = doGet(db, NAMESPACE, ID);
+            assertThat(outDocuments).containsExactly(DOCUMENT);
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void closeAndFlush() {
         mDb.close();
     }
@@ -137,4 +185,20 @@
     public void clearTestData() throws Exception {
         mDb.setSchema(new SetSchemaRequest.Builder().setForceOverride(true).build()).get();
     }
+
+    @Test
+    public void createSessionInStoppedUser() {
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            Bundle args = InstrumentationRegistry.getArguments();
+            int userId = Integer.parseInt(args.getString(USER_ID_KEY));
+            ExecutionException exception = expectThrows(ExecutionException.class, () ->
+                    AppSearchSessionShimImpl.createSearchSession(
+                            new AppSearchManager.SearchContext.Builder(DB_NAME).build(),
+                            userId).get());
+            assertThat(exception.getMessage()).contains("is locked or not running.");
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
index d397fc5..1fe992a 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchInstantAppTest.java
@@ -21,13 +21,12 @@
 import static org.testng.Assert.expectThrows;
 
 import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 import android.content.Context;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
index 3e91498..6e87b19 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperA/src/android/appsearch/app/a/AppSearchStorageAugmenterDeviceTest.java
@@ -21,6 +21,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.appsearch.AppSearchManager;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 import android.app.usage.StorageStats;
 import android.app.usage.StorageStatsManager;
 import android.content.Context;
@@ -29,8 +30,6 @@
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
diff --git a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
index fa9132d..1cbb442 100644
--- a/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
+++ b/hostsidetests/appsearch/test-apps/AppSearchHostTestHelperB/src/android/appsearch/app/b/AppSearchDeviceTest.java
@@ -16,18 +16,17 @@
 
 package android.appsearch.app.b;
 
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.SearchSpec;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/hostsidetests/appsecurity/Android.bp b/hostsidetests/appsecurity/Android.bp
index af53c8e..9a06caa 100644
--- a/hostsidetests/appsecurity/Android.bp
+++ b/hostsidetests/appsecurity/Android.bp
@@ -73,8 +73,3 @@
     name: "CtsHostsideTestsAppSecurityUtil",
     srcs: ["src/android/appsecurity/cts/Utils.java"],
 }
-
-filegroup {
-    name: "cts-ec-p256-por_1_2-default-caps.lineage",
-    srcs: ["certs/pkgsigverify/ec-p256-por_1_2-default-caps"],
-}
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index e4d9d04..056dc98 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -9,7 +9,7 @@
 per-file AppSecurityTests.java = cbrubaker@google.com
 per-file AuthBoundKeyTest.java = file:test-apps/AuthBoundKeyApp/OWNERS
 per-file BaseInstallMultiple.java = toddke@google.com,patb@google.com
-per-file CorruptApkTests.java = rtmitchell@google.com
+per-file CorruptApkTests.java = zyy@google.com
 per-file DeviceIdentifierTest.java = cbrubaker@google.com
 per-file EphemeralTest.java = toddke@google.com,patb@google.com
 per-file ExternalStorageHostTest.java = nandana@google.com
@@ -20,7 +20,7 @@
 per-file KeySetHostTest.java = cbrubaker@google.com
 per-file ListeningPortsTest.java = cbrubaker@google.com
 per-file MajorVersionTest.java = toddke@google.com,patb@google.com
-per-file OverlayHostTest.java = rtmitchell@google.com
+per-file OverlayHostTest.java = zyy@google.com
 per-file Package* = chiuwinson@google.com,patb@google.com,toddke@google.com
 per-file PermissionsHostTest.java = moltmann@google.com
 per-file Pkg* = chiuwinson@google.com,patb@google.com,toddke@google.com
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk
new file mode 100644
index 0000000..869f9a9
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig
new file mode 100644
index 0000000..fb9b02f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryServiceTest_v2-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk
new file mode 100644
index 0000000..d7a03f1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig
new file mode 100644
index 0000000..1e86976
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-legacyV4.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk
new file mode 100644
index 0000000..1139176
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig
new file mode 100644
index 0000000..1f137ba
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk
new file mode 100644
index 0000000..38ab895
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig
new file mode 100644
index 0000000..2773f50
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk
new file mode 100644
index 0000000..69a5170
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig
new file mode 100644
index 0000000..51738c7
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v2-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk
new file mode 100644
index 0000000..2f03585
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig
new file mode 100644
index 0000000..0581b7a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/CtsSignatureQueryService_v3-tgt-33.apk.idsig
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh b/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
index 0adf311..2f4c5f8 100644
--- a/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
+++ b/hostsidetests/appsecurity/res/pkgsigverify/generate-apks.sh
@@ -39,4 +39,9 @@
 apksigner sign --v2-signing-enabled true --v3-signing-enabled false --v4-signing-enabled  --key cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256.pk8 --cert cts/hostsidetests/appsecurity/certs/pkgsigverify/ec-p256.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-inc-to-v2-noninc-ec-p256-appv2.apk cts/hostsidetests/appsecurity/res/pkgsigverify/originalv2.apk
 
 # testInstallV4With* tests
-apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled --key vendor/google/certs/devkeys/devkey.pk8 --cert vendor/google/certs/devkeys/devkey.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-128bytes-additional-data.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
\ No newline at end of file
+apksigner sign --v2-signing-enabled false --v3-signing-enabled true --v4-signing-enabled --key vendor/google/certs/devkeys/devkey.pk8 --cert vendor/google/certs/devkeys/devkey.x509.pem -out cts/hostsidetests/appsecurity/res/pkgsigverify/v4-digest-v3-128bytes-additional-data.apk cts/hostsidetests/appsecurity/res/pkgsigverify/original.apk
+
+# testInstallV41* tests
+apksigner sign --in CtsSignatureQueryService_v2-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
+apksigner sign --in CtsSignatureQueryService_v3-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
+apksigner sign --in CtsSignatureQueryServiceTest_v2-tgt-33.apk --cert ../../certs/pkgsigverify/ec-p256.x509.pem --key ../../certs/pkgsigverify/ec-p256.pk8 --next-signer --cert ../../certs/pkgsigverify/ec-p256_2.x509.pem --key ../../certs/pkgsigverify/ec-p256_2.pk8 --lineage ../../certs/pkgsigverify/ec-p256-por_1_2-default-caps --rotation-min-sdk-version 33 --v4-signing-enabled
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk
new file mode 100644
index 0000000..3ca0c30
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-block-stripped-v3-attr-value-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk
new file mode 100644
index 0000000..20a2b0ab
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk
new file mode 100644
index 0000000..7129020
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-31-dev-release.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk
new file mode 100644
index 0000000..8a6a52c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk
new file mode 100644
index 0000000..0ffe9f1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk
new file mode 100644
index 0000000..c7bed06
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk
new file mode 100644
index 0000000..89475b8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33-modified.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk
new file mode 100644
index 0000000..c20792a
--- /dev/null
+++ b/hostsidetests/appsecurity/res/pkgsigverify/v31-ec-p256_2-tgt-33.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
index 620c9eb..96e36cf 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApkVerityInstallTest.java
@@ -16,13 +16,14 @@
 
 package android.appsecurity.cts;
 
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-
 import static com.android.compatibility.common.util.PropertyUtil.getFirstApiLevel;
 import static com.android.compatibility.common.util.PropertyUtil.getVendorApiLevel;
 
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.util.CddTest;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -39,6 +40,7 @@
 
 import junitparams.Parameters;
 
+@Presubmit
 @RunWith(DeviceParameterizedRunner.class)
 @AppModeFull
 public final class ApkVerityInstallTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
index 98150c4..a0137dc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/AppSecurityTests.java
@@ -23,8 +23,9 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
-import android.platform.test.annotations.RestrictedBuildTest;
 import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RestrictedBuildTest;
 
 import com.android.ddmlib.Log;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -41,6 +42,7 @@
  * Set of tests that verify various security checks involving multiple apps are
  * properly enforced.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class AppSecurityTests extends BaseAppSecurityTest {
 
@@ -85,6 +87,15 @@
     private static final String DUPLICATE_DECLARE_PERMISSION_PKG =
             "com.android.cts.duplicatepermissiondeclareapp";
 
+    private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK =
+            "CtsDuplicatePermissionDeclareApp_DifferentProtectionLevel.apk";
+    private static final String DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG =
+            "com.android.cts.duplicatepermission.differentprotectionlevel";
+    private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK =
+            "CtsDuplicatePermissionDeclareApp_SameProtectionLevel.apk";
+    private static final String DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG =
+            "com.android.cts.duplicatepermission.sameprotectionlevel";
+
     private static final String LOG_TAG = "AppSecurityTests";
 
     @Before
@@ -358,4 +369,34 @@
                     "appops get " + pkgName + " android:system_alert_window");
         }
     }
+
+    /**
+     * Tests that a single APK declaring duplicate permissions with different protection levels
+     * cannot be installed.
+     */
+    @Test
+    public void testInstallDuplicatePermission_differentProtectionLevel_fail() throws Exception {
+        try {
+            new InstallMultiple(false /* instant */)
+                    .addFile(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_APK)
+                    .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
+        } finally {
+            getDevice().uninstallPackage(DUPLICATE_PERMISSION_DIFFERENT_PROTECTION_LEVEL_PKG);
+        }
+    }
+
+    /**
+     * Tests that a single APK declaring duplicate permissions with the same protection level
+     * can be installed.
+     */
+    @Test
+    public void testInstallDuplicatePermission_sameProtectionLevel_success() throws Exception {
+        try {
+            new InstallMultiple(false /* instant */)
+                    .addFile(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_APK)
+                    .run(true /* expectingSuccess */);
+        } finally {
+            getDevice().uninstallPackage(DUPLICATE_PERMISSION_SAME_PROTECTION_LEVEL_PKG);
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
index 787d61e..63718f5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApplicationVisibilityTest.java
@@ -17,10 +17,11 @@
 package android.appsecurity.cts;
 
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
 /**
  * Tests the visibility of installed applications.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ApplicationVisibilityTest extends BaseAppSecurityTest {
 
@@ -319,6 +321,134 @@
                 testUserId);
     }
 
+    @Test
+    @AppModeFull(reason = "instant applications cannot be granted INTERACT_ACROSS_USERS")
+    public void testGetPackageUidCrossUserGrant() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+
+        final int installUserId = getInstallUserId();
+        final int testUserId = getTestUserId();
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put("testUser", Integer.toString(installUserId));
+
+        installTestAppForUser(TINY_APK, installUserId);
+        // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageUidVisibility_currentUser",
+                testUserId);
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageUidVisibility_anotherUserCrossUserGrant",
+                testUserId,
+                testArgs);
+    }
+
+    @Test
+    @AppModeFull(reason = "instant applications cannot see any other application")
+    public void testGetPackageUidCrossUserNoGrant() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+
+        final int installUserId = getInstallUserId();
+        final int testUserId = getTestUserId();
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put("testUser", Integer.toString(installUserId));
+
+        installTestAppForUser(TINY_APK, installUserId);
+        // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageUidVisibility_currentUser",
+                testUserId);
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageUidVisibility_anotherUserCrossUserNoGrant",
+                testUserId,
+                testArgs);
+    }
+
+    @Test
+    @AppModeFull(reason = "instant applications cannot be granted INTERACT_ACROSS_USERS")
+    public void testGetPackageGidsCrossUserGrant() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+
+        final int installUserId = getInstallUserId();
+        final int testUserId = getTestUserId();
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put("testUser", Integer.toString(installUserId));
+
+        installTestAppForUser(TINY_APK, installUserId);
+        // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageGidsVisibility_currentUser",
+                testUserId);
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageGidsVisibility_anotherUserCrossUserGrant",
+                testUserId,
+                testArgs);
+    }
+
+    @Test
+    @AppModeFull(reason = "instant applications cannot see any other application")
+    public void testGetPackageGidsCrossUserNoGrant() throws Exception {
+        if (!mSupportsMultiUser) {
+            return;
+        }
+
+        final int installUserId = getInstallUserId();
+        final int testUserId = getTestUserId();
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put("testUser", Integer.toString(installUserId));
+
+        installTestAppForUser(TINY_APK, installUserId);
+        // Also install TEST_WITH_PERMISSION_APK in installUSerId for creating across user context
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, installUserId);
+        installTestAppForUser(TEST_WITH_PERMISSION_APK, testUserId);
+
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageGidsVisibility_currentUser",
+                testUserId);
+        Utils.runDeviceTests(
+                getDevice(),
+                TEST_WITH_PERMISSION_PKG,
+                ".ApplicationVisibilityCrossUserTest",
+                "testGetPackageGidsVisibility_anotherUserCrossUserNoGrant",
+                testUserId,
+                testArgs);
+    }
+
     private int getInstallUserId() {
         return mUsers[0];
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
index b7d6bd5..ea66311 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DeviceIdentifierTest.java
@@ -17,6 +17,7 @@
 package android.appsecurity.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
index ab51c75..e513aa7 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DirectBootHostTest.java
@@ -51,7 +51,7 @@
     private static final String CLASS = PKG + ".EncryptionAppTest";
     private static final String APK = "CtsEncryptionApp.apk";
 
-    private static final String OTHER_APK = "CtsSplitApp29.apk";
+    private static final String OTHER_APK = "CtsSplitApp.apk";
     private static final String OTHER_PKG = "com.android.cts.splitapp";
 
     private static final String MODE_NATIVE = "native";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
index 91ec159..d41257a 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -22,6 +22,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -39,6 +40,7 @@
 /**
  * Tests for ephemeral packages.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull(reason = "Already handles instant installs when needed")
 public class EphemeralTest extends BaseAppSecurityTest {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 9848f8f..a918540 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -22,6 +22,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import android.platform.test.annotations.Presubmit;
+
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
 import com.android.role.RoleProto;
@@ -29,6 +31,7 @@
 import com.android.role.RoleUserStateProto;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.AbiUtils;
@@ -39,7 +42,6 @@
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -51,6 +53,7 @@
 /**
  * Set of tests that verify behavior of external storage devices.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class ExternalStorageHostTest extends BaseHostJUnit4Test {
     private static final String TAG = "ExternalStorageHostTest";
@@ -111,6 +114,7 @@
     private static final String FEATURE_WATCH = "android.hardware.type.watch";
 
     private int[] mUsers;
+    private boolean mAdbWasRoot;
 
     private File getTestAppFile(String fileName) throws FileNotFoundException {
         CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
@@ -122,6 +126,16 @@
         mUsers = Utils.prepareMultipleUsers(getDevice());
         assertNotNull(getAbi());
         assertNotNull(getBuild());
+
+        ITestDevice device = getDevice();
+        mAdbWasRoot = device.isAdbRoot();
+        if (mAdbWasRoot) {
+            // This test assumes that the test is not run with root privileges. But this test runs
+            // as a part of appsecurity test suite which contains a lot of other tests. Some of
+            // which may enable adb root, make sure that this test is run without root access.
+            device.disableAdbRoot();
+            assertFalse("adb root is enabled", device.isAdbRoot());
+        }
     }
 
     @Before
@@ -135,8 +149,25 @@
         wipePrimaryExternalStorage();
     }
 
+    @After
+    public void tearDown() throws DeviceNotAvailableException {
+        ITestDevice device = getDevice();
+        if (mAdbWasRoot) {
+            device.enableAdbRoot();
+        } else {
+            device.disableAdbRoot();
+        }
+    }
+
+    // b/215212818 - certain tests fail with fuse-bpf, so temporarily disable
+    private boolean isFuseBpf() throws Exception {
+        return "true".equals(getDevice()
+            .executeShellCommand("getprop persist.sys.fuse.bpf.enable").trim());
+    }
+
     @Test
     public void testExternalStorageRename() throws Exception {
+        Assume.assumeFalse(isFuseBpf()); // b/215212818
         try {
             wipePrimaryExternalStorage();
 
@@ -159,6 +190,7 @@
      */
     @Test
     public void testExternalStorageNone29() throws Exception {
+        Assume.assumeFalse(isFuseBpf()); // b/215212818
         try {
             wipePrimaryExternalStorage();
 
@@ -182,6 +214,7 @@
      */
     @Test
     public void testExternalStorageRead29() throws Exception {
+        Assume.assumeFalse(isFuseBpf()); // b/215212818
         try {
             wipePrimaryExternalStorage();
 
@@ -205,6 +238,7 @@
      */
     @Test
     public void testExternalStorageWrite() throws Exception {
+        Assume.assumeFalse(isFuseBpf()); // b/215212818
         try {
             wipePrimaryExternalStorage();
 
@@ -515,6 +549,55 @@
         }
     }
 
+    /**
+     * b/197302116. The apps can't be granted prefix UriPermissions to the uri, when the query
+     * result of the uri is 1.
+     */
+    @Test
+    public void testOwningOneFileNotGrantPrefixUriPermission() throws Exception {
+        Assume.assumeFalse(isFuseBpf()); // b/215212818
+        installPackage(MEDIA.apk);
+
+        int user = getDevice().getCurrentUser();
+
+        // revoke permissions
+        updatePermissions(MEDIA.pkg, user, new String[] {
+                PERM_READ_EXTERNAL_STORAGE,
+                PERM_WRITE_EXTERNAL_STORAGE,
+        }, false);
+
+
+        // revoke the app ops permission
+        updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
+        runDeviceTests(MEDIA.pkg, MEDIA.clazz,
+                "testOwningOneFileNotGrantPrefixUriPermission", user);
+    }
+
+    /**
+     * If the app grants read UriPermission to the uri without id (E.g.
+     * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the same
+     * without granting permission.
+     */
+    @Test
+    public void testReadUriPermissionOnUriWithoutId_sameQueryResult() throws Exception {
+        installPackage(MEDIA.apk);
+
+        int user = getDevice().getCurrentUser();
+
+        // revoke permissions
+        updatePermissions(MEDIA.pkg, user, new String[] {
+                PERM_READ_EXTERNAL_STORAGE,
+                PERM_WRITE_EXTERNAL_STORAGE,
+        }, false);
+
+
+        // revoke the app ops permission
+        updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
+        runDeviceTests(MEDIA.pkg, MEDIA.clazz,
+                "testReadUriPermissionOnUriWithoutId_sameQueryResult", user);
+    }
 
     @Test
     public void testGrantUriPermission() throws Exception {
@@ -627,20 +710,23 @@
     }
 
     @Test
-    @Ignore("Enable after b/197701722 is fixed")
     public void testMediaEscalation_RequestWriteFilePathSupport() throws Exception {
         // Not adding tests for MEDIA_28 and MEDIA_29 as they need W_E_S for write access via file
         // path for shared files, and will always have access as they have W_E_S.
         installPackage(MEDIA.apk);
 
         int user = getDevice().getCurrentUser();
+        // revoke all permissions
         updatePermissions(MEDIA.pkg, user, new String[] {
+                PERM_ACCESS_MEDIA_LOCATION,
                 PERM_READ_EXTERNAL_STORAGE,
-        }, true);
-        updatePermissions(MEDIA.pkg, user, new String[] {
                 PERM_WRITE_EXTERNAL_STORAGE,
         }, false);
 
+        // revoke the app ops permission
+        updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_MEDIA, false);
+        updateAppOp(MEDIA.pkg, user, APP_OPS_MANAGE_EXTERNAL_STORAGE, false);
+
         runDeviceTests(MEDIA.pkg, MEDIA.clazz, "testMediaEscalation_RequestWriteFilePathSupport",
                 user);
     }
@@ -739,6 +825,7 @@
      */
     @Test
     public void testCreateRequest_userDenied() throws Exception {
+        Assume.assumeFalse(isFuseBpf());
         installPackage(MEDIA.apk);
 
         int user = getDevice().getCurrentUser();
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
index dee0358..02dbf52 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/InstantAppUserTest.java
@@ -20,6 +20,7 @@
 import static org.junit.Assert.assertNull;
 
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -34,6 +35,7 @@
 /**
  * Tests for ephemeral packages.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull(reason = "Already handles instant installs when needed")
 public class InstantAppUserTest extends BaseHostJUnit4Test {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index 6852faf..1980900 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -17,6 +17,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -27,6 +28,7 @@
 
 import java.io.FileNotFoundException;
 
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class IsolatedSplitsTests extends BaseAppSecurityTest {
     private static final String PKG = "com.android.cts.isolatedsplitapp";
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
index 1090b90..6a2f7f8 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/OverlayHostTest.java
@@ -15,10 +15,11 @@
  */
 package android.appsecurity.cts;
 
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
@@ -27,6 +28,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull(reason = "Overlays cannot be instant apps")
 public class OverlayHostTest extends BaseAppSecurityTest {
@@ -60,12 +64,17 @@
     private static final String TEST_APP_APK = "CtsOverlayApp.apk";
     private static final String TEST_APP_PACKAGE = "com.android.cts.overlay.app";
     private static final String TEST_APP_CLASS = "com.android.cts.overlay.app.OverlayableTest";
+    private static final String OVERLAY_TARGET_TEST_APP_CLASS =
+            "com.android.cts.overlay.target.OverlayTargetTest";
 
     // Overlay states
     private static final String STATE_DISABLED = "STATE_DISABLED";
     private static final String STATE_ENABLED = "STATE_ENABLED";
     private static final String STATE_NO_IDMAP = "STATE_NO_IDMAP";
 
+    // test arguments
+    private static final String PARAM_START_SERVICE = "start_service";
+
     private static final long OVERLAY_WAIT_TIMEOUT = 10000; // 10 seconds
 
     @Before
@@ -157,6 +166,12 @@
         }
     }
 
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName,
+            HashMap<String, String> testArgs) throws Exception {
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), packageName, testClassName, testMethodName,
+                testArgs);
+    }
+
     /**
      * Overlays that target android and are not signed with the platform signature must not be
      * installed successfully.
@@ -313,4 +328,68 @@
         runOverlayDeviceTest(TARGET_OVERLAYABLE_APK, OVERLAY_ALL_APK, OVERLAY_ALL_PACKAGE,
                 testMethod);
     }
+
+    @Test
+    public void testOverlayEnabled_activityInForeground() throws Exception {
+        final HashMap<String, String> testArgs = new HashMap<>();
+        testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
+        try {
+            new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+            new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+            runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+                    "overlayEnabled_activityInForeground", testArgs);
+        } finally {
+            getDevice().uninstallPackage(TARGET_PACKAGE);
+            getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+        }
+    }
+
+    @Test
+    public void testOverlayEnabled_activityInBackground_toForeground() throws Exception {
+        final HashMap<String, String> testArgs = new HashMap<>();
+        testArgs.put(PARAM_START_SERVICE, Boolean.FALSE.toString());
+        try {
+            new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+            new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+            runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+                    "overlayEnabled_activityInBackground_toForeground", testArgs);
+        } finally {
+            getDevice().uninstallPackage(TARGET_PACKAGE);
+            getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+        }
+    }
+
+    @Test
+    public void testOverlayEnabled_activityWithServiceInForeground() throws Exception {
+        final HashMap<String, String> testArgs = new HashMap<>();
+        testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
+        try {
+            new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+            new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+            runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+                    "overlayEnabled_activityInForeground", testArgs);
+        } finally {
+            getDevice().uninstallPackage(TARGET_PACKAGE);
+            getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+        }
+    }
+
+    @Test
+    public void testOverlayEnabled_activityWithServiceInBackground_toForeground() throws Exception {
+        final HashMap<String, String> testArgs = new HashMap<>();
+        testArgs.put(PARAM_START_SERVICE, Boolean.TRUE.toString());
+        try {
+            new InstallMultiple().addFile(OVERLAY_ALL_APK).run();
+            new InstallMultiple().addFile(TARGET_OVERLAYABLE_APK).run();
+
+            runDeviceTests(TARGET_PACKAGE, OVERLAY_TARGET_TEST_APP_CLASS,
+                    "overlayEnabled_activityInBackground_toForeground", testArgs);
+        } finally {
+            getDevice().uninstallPackage(TARGET_PACKAGE);
+            getDevice().uninstallPackage(OVERLAY_ALL_PACKAGE);
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
index c932908..0e177bb 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PackageVisibilityTest.java
@@ -21,6 +21,8 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
+
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -31,6 +33,7 @@
 /**
  * Tests for visibility of packages installed in one user, in a different user.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class PackageVisibilityTest extends BaseAppSecurityTest {
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
index c110f7b..2e1c41e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -17,6 +17,7 @@
 package android.appsecurity.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.tradefed.build.IBuildInfo;
@@ -36,6 +37,7 @@
 /**
  * Tests for APK signature verification during installation.
  */
+@Presubmit
 public class PkgInstallSignatureVerificationTest extends DeviceTestCase implements IBuildReceiver {
 
     private static final String TEST_PKG = "android.appsecurity.cts.tinyapp";
@@ -705,6 +707,93 @@
                 "verifySignatures_withRotation_succeeds");
     }
 
+    public void testInstallV31UpdateAfterRotation() throws Exception {
+        // This test is the same as above, but using the v3.1 signature scheme for rotation.
+        assertInstallFromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallFromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        assertInstallSucceeds("CtsSignatureQueryService_v2-tgt-33.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+
+        assertInstallSucceeds("CtsSignatureQueryService_v3-tgt-33.apk");
+        assertInstallSucceeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+    }
+
+    public void testInstallV41UpdateAfterRotation() throws Exception {
+        // V4 is only enabled on devices with Incremental feature
+        if (!hasIncrementalFeature()) {
+            return;
+        }
+
+        // This test is the same as above, but using the v4.1 signature scheme for rotation.
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        assertInstallV4Succeeds("CtsSignatureQueryService_v2-tgt-33.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+
+        assertInstallV4Succeeds("CtsSignatureQueryService_v3-tgt-33.apk");
+        assertInstallV4Succeeds("CtsSignatureQueryServiceTest_v2-tgt-33.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_withRotation_succeeds");
+    }
+
+    public void testInstallV41WrongBlockId() throws Exception {
+        // V4 is only enabled on devices with Incremental feature
+        if (!hasIncrementalFeature()) {
+            return;
+        }
+
+        // This test is the same as above, but using the v4.1 signature scheme for rotation.
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongV41Block.apk",
+                "Failed to find V4 signature block corresponding to V3 blockId: 462663009");
+    }
+
+    public void testInstallV41LegacyV4() throws Exception {
+        // V4 is only enabled on devices with Incremental feature
+        if (!hasIncrementalFeature()) {
+            return;
+        }
+
+        // This test is the same as above, but using the v4.1 signature scheme for rotation.
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-legacyV4.apk",
+                "Failed to find V4 signature block corresponding to V3 blockId: 462663009");
+    }
+
+    public void testInstallV41WrongDigest() throws Exception {
+        // V4 is only enabled on devices with Incremental feature
+        if (!hasIncrementalFeature()) {
+            return;
+        }
+
+        // This test is the same as above, but using the v4.1 signature scheme for rotation.
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryService.apk");
+        assertInstallV4FromBuildSucceeds("CtsSignatureQueryServiceTest.apk");
+        Utils.runDeviceTests(getDevice(), SERVICE_TEST_PKG, SERVICE_TEST_CLASS,
+                "verifySignatures_noRotation_succeeds");
+
+        assertInstallV4FailsWithError("CtsSignatureQueryService_v2-tgt-33-wrongDigest.apk",
+                "APK digest in V4 signature does not match V2/V3");
+    }
+
     public void testInstallV3KeyRotationSigPerm() throws Exception {
         // tests that a v3 signed APK can still get a signature permission from an app with its
         // older signing certificate.
@@ -988,6 +1077,110 @@
         Utils.runDeviceTests(getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS, "testHasPerm");
     }
 
+    public void testV31TargetTPlatformUsesRotatedKey() throws Exception {
+        // The v3.1 signature block is intended to allow applications to target T+ for APK signing
+        // key rotation without needing multi-targeting APKs. This test verifies a standard APK
+        // install with the rotated key in the v3.1 signing block targeting T is recognized by the
+        // platform, and this rotated key is used as the signing identity.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testUsingRotatedSigner");
+    }
+
+    public void testV31TargetLaterThanDevicePlatformUsesOriginalKey() throws Exception {
+        // The v3.1 signature block allows targeting SDK versions later than T for rotation; for
+        // this test a target of 100001 is used assuming it will be beyond the platform's version.
+        // Since the target version for rotation is beyond the platform's version the original
+        // signer from the v3.0 block should be used.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-100001.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testUsingOriginalSigner");
+    }
+
+    public void testV31BlockStrippedWithV3StrippingProtectionAttrSet() throws Exception {
+        // With the introduction of the v3.1 signature scheme, a new stripping protection attribute
+        // has been added to the v3.0 signer to protect against stripping and modification of the
+        // v3.1 signing block. This test verifies a stripped v3.1 block is detected when the v3.0
+        // stripping protection attribute is set.
+        assertInstallFails("v31-block-stripped-v3-attr-value-33.apk");
+    }
+
+    public void testV31BlockWithMultipleSignersUsesCorrectSigner() throws Exception {
+        // All of the APKs for this test use multiple v3.1 signers; those targeting SDK versions
+        // expected to be outside the version of a device under test use the original signer, and
+        // those targeting an expected range for a device use the rotated key. This test is
+        // intended to ensure the signer with the min / max SDK version that matches the device
+        // SDK version is used.
+
+        // The APK used for this test contains two signers in the v3.1 signing block. The first
+        // has a range from 100001 to Integer.MAX_VALUE and is using the original signing key;
+        // the second targets 33 to 100000 using the rotated key.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-100001.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testUsingRotatedSigner");
+        uninstallPackage();
+
+        // The APK for this test contains two signers in the v3.1 block, one targeting SDK versions
+        // 1 to 27 using the original signer, and the other targeting 33+ using the rotated signer.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testUsingRotatedSigner");
+        uninstallPackage();
+
+        // This APK combines the extra signers from the APKs above, one targeting 1 to 27 with the
+        // original signing key, another targeting 100001+ with the original signing key, and the
+        // last targeting 33 to 100000 with the rotated key.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33-ec-p256-tgt-1-27-and-100001.apk");
+        Utils.runDeviceTests(
+                getDevice(), DEVICE_TESTS_PKG, DEVICE_TESTS_CLASS,
+                "testUsingRotatedSigner");
+    }
+
+    public void testV31UpdateV3ToFromV31Succeeds() throws Exception {
+        // Since the v3.1 block is just intended to allow targeting SDK versions T and later for
+        // rotation, an APK signed with the rotated key in a v3.0 signing block should support
+        // updates to an APK signed with the same signing key in a v3.1 signing block.
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+        uninstallPackage();
+
+        // Similarly an APK signed with the rotated key in a v3.1 signing block should support
+        // updates to an APK signed with the same signing key in a v3.0 signing block.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-33.apk");
+        assertInstallFromBuildSucceeds("v3-ec-p256-with-por_1_2-default-caps.apk");
+        uninstallPackage();
+    }
+
+    public void testV31RotationTargetModifiedReportedByV3() throws Exception {
+        // When determining if a signer in the v3.1 signing block should be applied, the min / max
+        // SDK versions from the signer are compared against the device's SDK version; if the device
+        // is not within the signer's range then the block is skipped, other v3.1 blocks are
+        // checked, and finally the v3.0 block is used. The v3.0 signer block contains an additional
+        // attribute with the rotation-min-sdk-version that was expected in the v3.1 signing
+        // block; if this attribute's value does not match what was found in the v3.1 block the
+        // APK should fail to install.
+        assertInstallFails("v31-ec-p256_2-tgt-33-modified.apk");
+    }
+
+    public void testV31RotationTargetsDevRelease() throws Exception {
+        // The v3.1 signature scheme allows targeting a platform release under development through
+        // the use of a rotation-targets-dev-release additional attribute. Since a platform under
+        // development shares the same SDK version as the most recently released platform, the
+        // attribute is used by the platform to determine if a signer block should be applied. If
+        // the signer's minSdkVersion is the same as the device's SDK version and this attribute
+        // is set, then the platform will check the value of ro.build.version.codename; a value of
+        // "REL" indicates the platform is a release platform, so the current signer block will not
+        // be used. During T's development, the SDK version is 31 and the codename is not "REL", so
+        // this test APK will install on T during development as well as after its release since
+        // the SDK version will be bumped at that point.
+        assertInstallSucceeds("v31-ec-p256_2-tgt-31-dev-release.apk");
+    }
+
+
     public void testInstallTargetSdk30WithV1Signers() throws Exception {
         // An app targeting SDK version >= 30 must have at least a V2 signature; this test verifies
         // an app targeting SDK version 30 with only a V1 signature fails to install.
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
index 9b0a243..2e569a5 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PrivilegedUpdateTests.java
@@ -18,6 +18,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.LargeTest;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
@@ -32,6 +33,7 @@
 /**
  * Tests that verify intent filters.
  */
+@Presubmit
 @LargeTest
 @AppModeFull(reason="Instant applications can never be system or privileged")
 public class PrivilegedUpdateTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
@@ -178,6 +180,25 @@
         }
     }
 
+    public void testUninstallDisabledUpdatedSystemApp_remainingDisabled() throws Exception {
+        if (!isDefaultAbi()) {
+            Log.w(TAG, "Skipping test for non-default abi.");
+            return;
+        }
+
+        getDevice().executeShellCommand("pm enable " + SHIM_PKG);
+        runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testPrivAppAndEnabled");
+        try {
+            assertNull(getDevice().installPackage(
+                    mBuildHelper.getTestFile(SHIM_UPDATE_APK), true));
+            getDevice().executeShellCommand("pm disable-user " + SHIM_PKG);
+            runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testUpdatedPrivAppAndDisabled");
+        } finally {
+            getDevice().uninstallPackage(SHIM_PKG);
+        }
+        runDeviceTests(TEST_PKG, ".PrivilegedAppDisableTest", "testPrivAppAndDisabled");
+    }
+
     private void runDeviceTests(String packageName, String testClassName, String testMethodName)
             throws DeviceNotAvailableException {
         Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
index 53d81c3..9b2d612 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ReadableSettingsFieldsTest.java
@@ -45,6 +45,9 @@
     private static final String TEST_CLASS = TEST_PACKAGE + ".ReadSettingsFieldsTest";
     private static final String TEST_APK = "CtsReadSettingsFieldsApp.apk";
     private static final String TEST_APK_TEST_ONLY = "CtsReadSettingsFieldsAppTestOnly.apk";
+    private static final String TEST_APK_TARGET_Q = "CtsReadSettingsFieldsAppTargetQ.apk";
+    private static final String TEST_APK_TARGET_R = "CtsReadSettingsFieldsAppTargetR.apk";
+    private static final String TEST_APK_TARGET_S = "CtsReadSettingsFieldsAppTargetS.apk";
 
     @Before
     public void setUp() throws Exception {
@@ -140,6 +143,25 @@
     }
 
     @Test
+    public void testSettingsKeysNotReadableForAfterR()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple().addFile(TEST_APK_TARGET_S).run();
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+                "testSettingsKeysNotReadableForAfterR");
+    }
+
+    @Test
+    public void testSettingsKeysReadableForRMinus()
+            throws DeviceNotAvailableException, FileNotFoundException {
+        new InstallMultiple().addFile(TEST_APK_TARGET_R).run();
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+                "testSettingsKeysReadableForRMinus");
+        new InstallMultiple().addFile(TEST_APK_TARGET_Q).run();
+        runDeviceTests(TEST_PACKAGE, TEST_CLASS,
+                "testSettingsKeysReadableForRMinus");
+    }
+
+    @Test
     public void testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation()
             throws DeviceNotAvailableException {
         runDeviceTests(TEST_PACKAGE, TEST_CLASS,
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
index db6124a..3bebcbc 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ResumeOnRebootHostTest.java
@@ -57,7 +57,7 @@
     private static final String CLASS = PKG + ".EncryptionAppTest";
     private static final String APK = "CtsEncryptionApp.apk";
 
-    private static final String OTHER_APK = "CtsSplitApp29.apk";
+    private static final String OTHER_APK = "CtsSplitApp.apk";
     private static final String OTHER_PKG = "com.android.cts.splitapp";
 
     private static final String FEATURE_REBOOT_ESCROW = "feature:android.hardware.reboot_escrow";
@@ -422,8 +422,8 @@
 
     private void deviceDisableDeviceConfigSync() throws Exception {
         getDevice().executeShellCommand("device_config set_sync_disabled_for_tests persistent");
-        String res = getDevice().executeShellCommand("device_config is_sync_disabled_for_tests");
-        if (res == null || !res.contains("true")) {
+        String res = getDevice().executeShellCommand("device_config get_sync_disabled_for_tests");
+        if (res == null || !res.contains("persistent")) {
             CLog.w(TAG, "Could not disable device config for test");
         }
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
index 8d1024a..b9b16ce 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SharedUserIdTest.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertNotNull;
 
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.ddmlib.Log;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
@@ -30,6 +31,7 @@
 /**
  * Set of tests that verify behavior related to apps with shared user IDs
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class SharedUserIdTest extends BaseAppSecurityTest {
 
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
index 5ed4aea..4d0150e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SplitTests.java
@@ -20,6 +20,7 @@
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.util.CpuFeatures;
 import com.android.tradefed.device.DeviceNotAvailableException;
@@ -41,6 +42,7 @@
 /**
  * Tests that verify installing of various split APKs from host side.
  */
+@Presubmit
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class SplitTests extends BaseAppSecurityTest {
     static final String PKG_NO_RESTART = "com.android.cts.norestart";
@@ -51,6 +53,19 @@
     static final String APK_NEED_SPLIT_FEATURE_WARM = "CtsNeedSplitFeatureWarm.apk";
     static final String APK_NEED_SPLIT_CONFIG = "CtsNeedSplitApp_xxhdpi-v4.apk";
 
+    static final String APK_REQUIRED_SPLIT_TYPE_BASE = "CtsRequiredSplitTypeSplitApp.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED =
+            "CtsRequiredSplitTypeSplitAppUpdated.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_DENSITY = "CtsSplitAppTypeDensity.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_LOCALE = "CtsSplitAppTypeLocale.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_FOO = "CtsSplitAppTypeFoo.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_MULTIPLE = "CtsSplitAppTypeMultiple.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_FEATURE = "CtsSplitAppTypeFeature.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA = "CtsSplitAppTypeFeatureData.apk";
+    static final String APK_REQUIRED_SPLIT_TYPE_FEATURE_FOO = "CtsSplitAppTypeFeatureFoo.apk";
+    static final String APK_INVALID_REQUIRED_SPLIT_TYPE_BASE =
+            "CtsInvalidRequiredSplitTypeSplitApp.apk";
+
     static final String PKG = "com.android.cts.splitapp";
     static final String CLASS = PKG + ".SplitAppTest";
 
@@ -710,6 +725,170 @@
                 .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
     }
 
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledIncomplete_full() throws Exception {
+        testRequiredSplitTypesInstalledIncomplete(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledIncomplete_instant() throws Exception {
+        testRequiredSplitTypesInstalledIncomplete(true);
+    }
+    private void testRequiredSplitTypesInstalledIncomplete(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInvalidRequiredSplitTypes_full() throws Exception {
+        testInvalidRequiredSplitTypes(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInvalidRequiredSplitTypes_instant() throws Exception {
+        testInvalidRequiredSplitTypes(true);
+    }
+    private void testInvalidRequiredSplitTypes(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_INVALID_REQUIRED_SPLIT_TYPE_BASE)
+                .runExpectingFailure("INSTALL_PARSE_FAILED_MANIFEST_MALFORMED");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledAll_full() throws Exception {
+        testRequiredSplitTypesInstalledAll(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledAll_instant() throws Exception {
+        testRequiredSplitTypesInstalledAll(true);
+    }
+    private void testRequiredSplitTypesInstalledAll(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledMultipleOne_full() throws Exception {
+        testRequiredSplitTypesInstalledMultipleOne(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitTypesInstalledMultipleOne_instant() throws Exception {
+        testRequiredSplitTypesInstalledMultipleOne(true);
+    }
+    private void testRequiredSplitTypesInstalledMultipleOne(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_MULTIPLE)
+                .run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitTypesRemoved_full() throws Exception {
+        testRequiredSplitTypesRemoved(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitTypesRemoved_instant() throws Exception {
+        testRequiredSplitTypesRemoved(true);
+    }
+    private void testRequiredSplitTypesRemoved(boolean instant) throws Exception {
+        // start with a base and three splits
+        new InstallMultiple(instant)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FOO)
+                .run();
+        // it's okay to remove non-required split type
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.foo").run();
+        // but, not to remove a required one
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("config.de")
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInheritUpdatedBase_requiredSplitTypesMissing_full() throws Exception {
+        testInheritUpdatedBase_requiredSplitTypesMissing(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedBase_requiredSplitTypesMissing_instant() throws Exception {
+        testInheritUpdatedBase_requiredSplitTypesMissing(true);
+    }
+    private void testInheritUpdatedBase_requiredSplitTypesMissing(boolean instant)
+            throws Exception {
+        // start with a base and the required splits
+        new InstallMultiple(instant)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .run();
+        // the updated base requires a new split type, but it's missing
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED)
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testInheritUpdatedBase_requiredSplitTypesInstalled_full() throws Exception {
+        testInheritUpdatedBase_requiredSplitTypesInstalled(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testInheritUpdatedBase_requiredSplitTypesInstalled_instant() throws Exception {
+        testInheritUpdatedBase_requiredSplitTypesInstalled(true);
+    }
+    private void testInheritUpdatedBase_requiredSplitTypesInstalled(boolean instant)
+            throws Exception {
+        // start with a base and the required splits
+        new InstallMultiple(instant)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .run();
+        // the updated base requires a split type, and this split also requires another.
+        new InstallMultiple(instant).inheritFrom(PKG)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_BASE_UPDATED)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA)
+                .run();
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testRequiredSplitTypesFromSplit_full() throws Exception {
+        testRequiredSplitTypesFromSplit(false);
+    }
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testRequiredSplitTypesFromSplit_instant() throws Exception {
+        testRequiredSplitTypesFromSplit(true);
+    }
+    private void testRequiredSplitTypesFromSplit(boolean instant) throws Exception {
+        new InstallMultiple(instant)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_BASE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_DENSITY)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_LOCALE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_DATA)
+                .addFile(APK_REQUIRED_SPLIT_TYPE_FEATURE_FOO)
+                .run();
+        // it's okay to remove non-required split type for the split
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_foo.foo").run();
+        // but, not to remove a required one for the split
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_foo.data")
+                .runExpectingFailure("INSTALL_FAILED_MISSING_SPLIT");
+    }
+
     /**
      * Verify that installing a new version of app wipes code cache.
      */
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index 7f4cf27..ff1c433 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -16,6 +16,8 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assume.assumeTrue;
+
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.TestDescription;
@@ -31,7 +33,6 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import static org.junit.Assume.assumeTrue;
 
 import java.util.Map;
 
@@ -43,11 +44,15 @@
     private static final String PKG_STATS = "com.android.cts.storagestatsapp";
     private static final String PKG_A = "com.android.cts.storageapp_a";
     private static final String PKG_B = "com.android.cts.storageapp_b";
+    private static  final String PKG_NO_APP_STORAGE = "com.android.cts.noappstorage";
     private static final String APK_STATS = "CtsStorageStatsApp.apk";
     private static final String APK_A = "CtsStorageAppA.apk";
     private static final String APK_B = "CtsStorageAppB.apk";
+    private static final String APK_NO_APP_STORAGE = "CtsNoAppDataStorageApp.apk";
     private static final String CLASS_STATS = "com.android.cts.storagestatsapp.StorageStatsTest";
     private static final String CLASS = "com.android.cts.storageapp.StorageTest";
+    private static final String CLASS_NO_APP_STORAGE =
+            "com.android.cts.noappstorage.NoAppDataStorageTest";
 
     private int[] mUsers;
 
@@ -58,6 +63,7 @@
         installPackage(APK_STATS);
         installPackage(APK_A);
         installPackage(APK_B);
+        installPackage(APK_NO_APP_STORAGE);
 
         for (int user : mUsers) {
             getDevice().executeShellCommand("appops set --user " + user + " " + PKG_STATS
@@ -72,6 +78,7 @@
         getDevice().uninstallPackage(PKG_STATS);
         getDevice().uninstallPackage(PKG_A);
         getDevice().uninstallPackage(PKG_B);
+        getDevice().uninstallPackage(PKG_NO_APP_STORAGE);
     }
 
     @Test
@@ -223,6 +230,16 @@
         }
     }
 
+    @Test
+    public void testNoInternalAppStorage() throws Exception {
+        for (int user : mUsers) {
+            runDeviceTests(
+                    PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoInternalCeStorage", user);
+            runDeviceTests(
+                    PKG_NO_APP_STORAGE, CLASS_NO_APP_STORAGE, "testNoInternalDeStorage", user);
+        }
+    }
+
     public void waitForIdle() throws Exception {
         // Try getting all pending events flushed out
         for (int i = 0; i < 4; i++) {
diff --git a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
index 042e74a..0e81d2b 100644
--- a/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
+++ b/hostsidetests/appsecurity/test-apps/ApplicationVisibilityCrossUserApp/src/com/android/cts/applicationvisibility/ApplicationVisibilityCrossUserTest.java
@@ -194,6 +194,100 @@
         } catch (SecurityException e) {}
     }
 
+    /**
+     * Tests getting the uid of the installed packages for the current user.
+     **/
+    @Test
+    public void testGetPackageUidVisibility_currentUser() {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            pm.getPackageUid(TINY_PKG, 0 /*flags*/);
+            fail("Should have received a NameNotFoundException");
+        } catch (PackageManager.NameNotFoundException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Tests getting the uid of the installed packages for primary user,
+     * with cross user permission granted.
+     **/
+    @Test
+    public void testGetPackageUidVisibility_anotherUserCrossUserGrant() {
+        final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+                0 /*flags*/).getPackageManager();
+        try {
+            pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+        } catch (PackageManager.NameNotFoundException e) {
+            fail("Should not receive a NameNotFoundException");
+        }
+    }
+
+    /**
+     * Tests getting the uid of the installed packages for primary user,
+     * with cross user permission revoked.
+     **/
+    @Test
+    public void testGetPackageUidVisibility_anotherUserCrossUserNoGrant()
+            throws PackageManager.NameNotFoundException {
+        final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+                0 /*flags*/).getPackageManager();
+        ungrantAcrossUsersPermission();
+        try {
+            pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+            fail("Should have received a SecurityException");
+        } catch (SecurityException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Tests getting the gids of the installed packages for the current user.
+     **/
+    @Test
+    public void testGetPackageGidsVisibility_currentUser() {
+        final PackageManager pm = mContext.getPackageManager();
+        try {
+            pm.getPackageGids(TINY_PKG, 0 /*flags*/);
+            fail("Should have received a NameNotFoundException");
+        } catch (PackageManager.NameNotFoundException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Tests getting the gids of the installed packages for primary user,
+     * with cross user permission granted.
+     **/
+    @Test
+    public void testGetPackageGidsVisibility_anotherUserCrossUserGrant() {
+        final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+                0 /*flags*/).getPackageManager();
+        try {
+            pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+        } catch (PackageManager.NameNotFoundException e) {
+            fail("Should not receive a NameNotFoundException");
+        }
+    }
+
+    /**
+     * Tests getting the gids of the installed packages for primary user,
+     * with cross user permission revoked.
+     **/
+    @Test
+    public void testGetPackageGidsVisibility_anotherUserCrossUserNoGrant()
+            throws PackageManager.NameNotFoundException {
+        final PackageManager pm = mContext.createContextAsUser(UserHandle.of(getTestUser()),
+                0 /*flags*/).getPackageManager();
+        ungrantAcrossUsersPermission();
+        try {
+            pm.getPackageUid(TINY_PKG, MATCH_KNOWN_PACKAGES);
+            fail("Should have received a SecurityException");
+        } catch (SecurityException e) {
+            // Expected
+        }
+    }
+
     private boolean isAppInPackageList(String packageName,
             List<PackageInfo> packageList) {
         for (PackageInfo pkgInfo : packageList) {
diff --git a/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS b/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
index 21cd9d9..870d1a4 100644
--- a/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/CorruptApkTests/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index ef54b1a..ed22bab 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -23,6 +23,7 @@
     defaults: ["cts_support_defaults"],
     sdk_version: "test_current",
     static_libs: [
+        "androidx.appcompat_appcompat",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 1fecc44..0df4795 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
 
 import com.android.cts.documentclient.MyActivity.Result;
 
@@ -71,6 +72,7 @@
     private static final String TEST_TARGET_DIRECTORY_PATH =
             TEST_SOURCE_DIRECTORY_PATH + File.separatorChar + TEST_TARGET_DIRECTORY_NAME;
     private static final String STORAGE_AUTHORITY = "com.android.externalstorage.documents";
+    private static final String DEFAULT_DEVICE_NAME = "Internal storage";
 
     private UiSelector findRootListSelector() throws UiObjectNotFoundException {
         return new UiSelector().resourceId(
@@ -173,19 +175,19 @@
         return new UiObject(new UiSelector().resourceId("android:id/button1"));
     }
 
-    private void assertToolbarTitleEquals(String label) throws UiObjectNotFoundException {
+    private boolean checkToolbarTitleEquals(String label) throws UiObjectNotFoundException {
         final UiObject title = new UiObject(new UiSelector().resourceId(
                 getDocumentsUiPackageId() + ":id/toolbar").childSelector(
                 new UiSelector().className("android.widget.TextView").text(label)));
 
-        assertTrue(title.waitForExists(TIMEOUT));
+        return title.waitForExists(TIMEOUT);
     }
 
     private String getDeviceName() {
         final String deviceName = Settings.Global.getString(
                 mActivity.getContentResolver(), Settings.Global.DEVICE_NAME);
-        // Device name should always be set. In case it isn't, fall back to "Internal Storage"
-        return !TextUtils.isEmpty(deviceName) ? deviceName : "Internal Storage";
+        // Device name should always be set. In case it isn't, fall back to "Internal storage"
+        return !TextUtils.isEmpty(deviceName) ? deviceName : DEFAULT_DEVICE_NAME;
     }
 
     @Override
@@ -431,15 +433,25 @@
         // save button is disabled for the storage root
         assertFalse(findSaveButton().isEnabled());
 
-        // We should always have Android directory available
-        findDocument("Android").click();
-        mDevice.waitForIdle();
+        // SAF directory access to /Android is blocked in ag/13163842, this change may not be
+        // present on some R devices, so only test it from above S.
+        if (BuildCompat.isAtLeastS()) {
+            // We should always have Android directory available
+            findDocument("Android").click();
+            mDevice.waitForIdle();
 
-        // save button is disabled for Android folder
-        assertFalse(findSaveButton().isEnabled());
+            // save button is disabled for Android folder
+            assertFalse(findSaveButton().isEnabled());
+        }
 
-        findRoot(getDeviceName()).click();
-        mDevice.waitForIdle();
+        try {
+            findRoot(getDeviceName()).click();
+            mDevice.waitForIdle();
+        } catch(UiObjectNotFoundException e) {
+            // It might be possible that OEMs customize storage root to "Internal storage".
+            findRoot(DEFAULT_DEVICE_NAME).click();
+            mDevice.waitForIdle();
+        }
 
         try {
             findDocument("Download").click();
@@ -486,8 +498,14 @@
         // save button is enabled for Android folder
         assertTrue(findSaveButton().isEnabled());
 
-        findRoot(getDeviceName()).click();
-        mDevice.waitForIdle();
+        try {
+            findRoot(getDeviceName()).click();
+            mDevice.waitForIdle();
+        } catch(UiObjectNotFoundException e) {
+            // It might be possible that OEMs customize storage root to "Internal storage".
+            findRoot(DEFAULT_DEVICE_NAME).click();
+            mDevice.waitForIdle();
+        }
 
         try {
             findDocument("Download").click();
@@ -804,8 +822,9 @@
         mActivity.startActivityForResult(intent, REQUEST_CODE);
         mDevice.waitForIdle();
 
-        // assert the default root is internal storage root
-        assertToolbarTitleEquals(getDeviceName());
+        // assert toolbar title should be set to either device name or "Internal storage".
+        assertTrue(checkToolbarTitleEquals(getDeviceName())
+            || checkToolbarTitleEquals(DEFAULT_DEVICE_NAME));
 
         // no Downloads root
         assertFalse(findRoot("Downloads").exists());
@@ -974,6 +993,9 @@
         assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
         assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
 
+        // Multiple calls to this method too quickly may result in onActivityResult being skipped.
+        mDevice.waitForIdle();
+
         return uri;
     }
 
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
index 5f80327..d667402 100644
--- a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/Android.bp
@@ -30,3 +30,23 @@
         "general-tests",
     ],
 }
+
+android_test_import {
+    name: "CtsDuplicatePermissionDeclareApp_DifferentProtectionLevel",
+    apk: "apk/b211934395_DifferentProtectionLevel.apk",
+    presigned: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_import {
+    name: "CtsDuplicatePermissionDeclareApp_SameProtectionLevel",
+    apk: "apk/b211934395_SameProtectionLevel.apk",
+    presigned: true,
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk
new file mode 100644
index 0000000..e25d176
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_DifferentProtectionLevel.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk
new file mode 100644
index 0000000..15e6a5d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/DuplicatePermissionDeclareApp/apk/b211934395_SameProtectionLevel.apk
Binary files differ
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
index 07ec92a..74403bc 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/Android.bp
@@ -57,6 +57,7 @@
 android_test {
     name: "CtsSignatureQueryServiceTest_v2",
     defaults: ["cts_support_defaults"],
+    manifest: "AndroidManifest_v2.xml",
     compile_multilib: "both",
     sdk_version: "current",
     srcs: ["src/**/*.java"],
@@ -75,7 +76,7 @@
     ],
     certificate: ":ec-p256_2",
     additional_certificates: [":ec-p256"],
-    lineage : ":ec-p256-por_1_2-default-caps",
+    lineage: ":ec-p256-por_1_2-default-caps",
     v4_signature: true,
     // Disable dexpreopt and <uses-library> check for test
     enforce_uses_libs: false,
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
index e4981ee..0436f08 100644
--- a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest.xml
@@ -14,7 +14,8 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.appsecurity.cts.keyrotationtest.test">
+    package="android.appsecurity.cts.keyrotationtest.test"
+    android:versionCode="1">
   <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                    android:targetPackage="android.appsecurity.cts.keyrotationtest" />
   <application/>
diff --git a/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml
new file mode 100644
index 0000000..3206a52
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/KeyRotationTest/ServiceTest/AndroidManifest_v2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.appsecurity.cts.keyrotationtest.test"
+    android:versionCode="2">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.appsecurity.cts.keyrotationtest" />
+    <application/>
+</manifest>
+
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index 36ad932..b3d1d1c 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -16,9 +16,12 @@
 
 package com.android.cts.mediastorageapp;
 
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
+
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -70,6 +73,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.concurrent.Callable;
@@ -124,7 +129,7 @@
 
         final HashSet<Long> seen = new HashSet<>();
         try (Cursor c = mContentResolver.query(
-                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL),
+                MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
                 new String[] { MediaColumns._ID }, null, null)) {
             while (c.moveToNext()) {
                 seen.add(c.getLong(0));
@@ -217,10 +222,191 @@
     }
 
     /**
-     * Test prefix and non-prefix uri grant for all packages
+     * If the app grants read UriPermission to the uri without id (E.g.
+     * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the
+     * same without granting permission.
      */
     @Test
-    public void testGrantUriPermission() {
+    public void testReadUriPermissionOnUriWithoutId_sameQueryResult() throws Exception {
+        // For Audio, Image, Video, If the app doesn't have delete access to the uri,
+        // MediaProvider throws SecurityException to give callers interacting with a specific media
+        // item a chance to escalate access if they don't already have it. Check SecurityException
+        // for them.
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createVideo,/* checkExceptionForDelete= */  true);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createImage,/* checkExceptionForDelete= */  true);
+
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Audio.Artists.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Audio.Genres.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createAudio, /* checkExceptionForDelete= */ true);
+
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createDownload,/* checkExceptionForDelete= */  false);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                MediaStorageTest::createFile,/* checkExceptionForDelete= */  false);
+        doReadUriPermissionOnUriWithoutId_sameQueryResult(
+                MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createPlaylist,/* checkExceptionForDelete= */  false);
+    }
+
+    /**
+     * If the app grants read UriPermission to the uri without id (E.g.
+     * MediaStore.Audio.Media.EXTERNAL_CONTENT_URI), the query result of the uri should be the
+     * same without granting permission.
+     */
+    private void doReadUriPermissionOnUriWithoutId_sameQueryResult(Uri collectionUri,
+            Callable<Uri> create, boolean checkExceptionForDelete) throws Exception {
+        final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
+        final Uri red = create.call();
+        final Uri blue = create.call();
+        clearMediaOwner(blue, mUserId);
+        final int originalCount;
+
+        try {
+            try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID},
+                    null, null)) {
+                originalCount = c.getCount();
+            }
+
+            mContext.grantUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead);
+            try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID},
+                    null, null)) {
+                assertWithMessage("After grant read UriPermission to " + collectionUri.toString()
+                        + ", the item count of the query result" ).that(c.getCount()).isEqualTo(
+                        originalCount);
+            }
+
+            try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+            }
+
+            try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+                fail("Expected read access to " + blue.toString() + " be blocked");
+            } catch (SecurityException | FileNotFoundException expected) {
+            }
+
+            try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+                fail("Expected write access to " + blue.toString() + " be blocked");
+            } catch (SecurityException | FileNotFoundException expected) {
+            }
+
+            // If checkExceptionForDelete is true, throws SecurityException is as we expected.
+            // Otherwise, the app doesn't have delete access to the file, the deleted count is 0.
+            if (checkExceptionForDelete) {
+                try {
+                    mContentResolver.delete(blue, null);
+                    fail("Expected delete access to " + blue.toString() + " be blocked");
+                } catch (SecurityException expected) {
+                }
+            } else {
+                final int count = mContentResolver.delete(blue, null);
+                assertThat(count).isEqualTo(0);
+            }
+        } finally {
+            mContext.revokeUriPermission(mContext.getPackageName(), collectionUri, flagGrantRead);
+        }
+    }
+
+    /**
+     * b/197302116. The apps can't be granted prefix UriPermissions to the uri, when the query
+     * result of the uri is 1.
+     */
+    @Test
+    public void testOwningOneFileNotGrantPrefixUriPermission() throws Exception {
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createAudio);
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createVideo);
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createImage);
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createDownload);
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                MediaStorageTest::createFile);
+        doOwningOneFileNotGrantPrefixUriPermission(MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI,
+                MediaStorageTest::createPlaylist);
+    }
+
+    /**
+     * The apps can't be granted prefix UriPermissions to the uri without id, when the query result
+     * of the uri is 1.
+     */
+    private void doOwningOneFileNotGrantPrefixUriPermission(Uri collectionUri, Callable<Uri> create)
+            throws Exception {
+
+        clearOwnFiles(collectionUri);
+
+        final Uri red = create.call();
+        final Uri blue = create.call();
+        clearMediaOwner(blue, mUserId);
+
+        try (Cursor c = mContentResolver.query(collectionUri, new String[]{MediaColumns._ID}, null,
+                null)) {
+            assertThat(c.getCount()).isEqualTo(1);
+            c.moveToFirst();
+            assertThat(c.getLong(0)).isEqualTo(ContentUris.parseId(red));
+        }
+
+        final int flagGrantReadPrefix =
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        try {
+            mContext.grantUriPermission(mContext.getPackageName(), collectionUri,
+                    flagGrantReadPrefix);
+            fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x"
+                    + Integer.toHexString(flagGrantReadPrefix));
+        } catch (SecurityException expected) {
+        }
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "r")) {
+        }
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "r")) {
+            fail("Expected read access to " + blue.toString() + " be blocked");
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+
+        final int flagGrantWritePrefix = Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        try {
+            mContext.grantUriPermission(mContext.getPackageName(), collectionUri,
+                    flagGrantWritePrefix);
+            fail("Expected granting to " + collectionUri.toString() + " be blocked for flag 0x"
+                    + Integer.toHexString(flagGrantWritePrefix));
+        } catch (SecurityException expected) {
+        }
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "rw")) {
+        }
+
+        try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(blue, "w")) {
+            fail("Expected write access to " + blue.toString() + " be blocked");
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+
+    @Test
+    public void testGrantUriPermission() throws Exception {
+        doGrantUriPermission_nonPrefixAndPrefix();
+        doGrantUriPermission_prefix();
+    }
+
+    /**
+     * Test prefix and non-prefix uri grant for all packages
+     */
+    private void doGrantUriPermission_nonPrefixAndPrefix() {
         final int flagGrantRead = Intent.FLAG_GRANT_READ_URI_PERMISSION;
         final int flagGrantWrite = Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
         final int flagGrantReadPrefix =
@@ -228,20 +414,44 @@
         final int flagGrantWritePrefix =
                 Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
 
-        for (Uri uri : new Uri[] {
+        for (Uri uri : new Uri[]{
                 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                 MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                 MediaStore.Downloads.EXTERNAL_CONTENT_URI,
-                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
+                MediaStore.Files.getContentUri(VOLUME_EXTERNAL)
         }) {
             // Non-prefix grant
-            checkGrantUriPermission(uri, flagGrantRead, true);
-            checkGrantUriPermission(uri, flagGrantWrite, true);
+            checkGrantUriPermission(uri, flagGrantRead, /* isGrantAllowed */ true);
+            checkGrantUriPermission(uri, flagGrantWrite, /* isGrantAllowed */ true);
 
             // Prefix grant
-            checkGrantUriPermission(uri, flagGrantReadPrefix, false);
-            checkGrantUriPermission(uri, flagGrantWritePrefix, false);
+            checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false);
+            checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false);
+
+            // revoke granted permissions
+            mContext.revokeUriPermission(uri, flagGrantRead | flagGrantWrite);
+        }
+    }
+
+    /**
+     * b/194539422. Test prefix uri grant for all packages
+     */
+    private void doGrantUriPermission_prefix() {
+        final int flagGrantReadPrefix =
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+        final int flagGrantWritePrefix =
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
+        for (Uri uri : new Uri[]{
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
+                MediaStore.Downloads.EXTERNAL_CONTENT_URI,
+                MediaStore.Files.getContentUri(VOLUME_EXTERNAL)
+        }) {
+            checkGrantUriPermission(uri, flagGrantReadPrefix, /* isGrantAllowed */ false);
+            checkGrantUriPermission(uri, flagGrantWritePrefix, /* isGrantAllowed */ false);
         }
     }
 
@@ -251,7 +461,8 @@
         } else {
             try {
                 mContext.grantUriPermission(mContext.getPackageName(), uri, mode);
-                fail("Expected granting to be blocked for flag 0x" + Integer.toHexString(mode));
+                fail("Expected granting to " + uri.toString() + " be blocked for flag 0x"
+                        + Integer.toHexString(mode));
             } catch (SecurityException expected) {
             }
         }
@@ -438,15 +649,14 @@
 
         try (ParcelFileDescriptor pfd = mContentResolver.openFileDescriptor(red, "w")) {
         }
-        // Wait for MediaStore to be idle to avoid flakiness due to race conditions between
-        // MediaStore.scanFile (which is called above in #openFileDescriptor) and rename (which is
-        // called below). This is a known issue: b/158982091
+        // Wait for MediaStore to be idle to avoid flakiness due to race conditions
         MediaStore.waitForIdle(mContentResolver);
 
         // Check File API support
         assertAccessFileAPISupport(file);
         assertReadWriteFileAPISupport(file);
         assertRenameFileAPISupport(file);
+        assertRenameAndReplaceFileAPISupport(file, create);
         assertDeleteFileAPISupport(file);
     }
 
@@ -471,15 +681,52 @@
     public void assertRenameFileAPISupport(File oldFile) throws Exception {
         final String oldName = oldFile.getAbsolutePath();
         final String extension = oldName.substring(oldName.lastIndexOf('.')).trim();
-        // TODO(b/178816495): Changing the extension changes the media-type and hence the media-URI
-        // corresponding to the new file is not accessible to the caller. Rename to the same
-        // extension so that the test app does not lose access and is able to delete the file.
-        final String newName = "cts" + System.nanoTime() + extension;
-        final File newFile = Environment.buildPath(Environment.getExternalStorageDirectory(),
-                Environment.DIRECTORY_DOWNLOADS, newName);
-        assertThat(oldFile.renameTo(newFile)).isTrue();
+        // Rename to same extension so test app does not lose access to file.
+        final String newRelativeName = "cts" + System.nanoTime() + extension;
+        final File newFile = Environment.buildPath(
+            Environment.getExternalStorageDirectory(),
+            Environment.DIRECTORY_DOWNLOADS,
+            newRelativeName);
+        final String newName = newFile.getAbsolutePath();
+        assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isTrue();
         // Rename back to oldFile for other ops like delete
-        assertThat(newFile.renameTo(oldFile)).isTrue();
+        assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+            .that(newFile.renameTo(oldFile))
+            .isTrue();
+    }
+
+    public void assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create)
+            throws Exception {
+        final String oldName = oldFile.getAbsolutePath();
+
+        // Create new file to which we do not have any access.
+        final Uri newUri = create.call();
+        assertWithMessage("Check newFile created").that(newUri).isNotNull();
+        File newFile = new File(queryForSingleColumn(newUri, MediaColumns.DATA));
+        final String newName = newFile.getAbsolutePath();
+        clearMediaOwner(newUri, mUserId);
+
+        assertWithMessage(
+            "Rename should fail without newFile grant from oldName [%s] to newName [%s]",
+            oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isFalse();
+
+        // Grant access to newFile and rename should succeed.
+        doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+        assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isTrue();
+
+        // We need to request grant on newUri again, since the rename above caused the URI grant
+        // to be revoked.
+        doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+        // Rename back to oldFile for other ops like delete
+        assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+            .that(newFile.renameTo(oldFile))
+            .isTrue();
     }
 
     private void assertDeleteFileAPISupport(File file) throws Exception {
@@ -783,12 +1030,38 @@
         }
     }
 
+    private static Uri createDownload() throws IOException {
+        final String content = "<html><body>Content</body></html>";
+        final String displayName = "cts" + System.nanoTime();
+        final String mimeType = "text/html";
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final PendingParams params = new PendingParams(
+                MediaStore.Downloads.EXTERNAL_CONTENT_URI, displayName, mimeType);
+
+        final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+        assertNotNull(pendingUri);
+        try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
+            try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
+                pw.print(content);
+            }
+            try (OutputStream out = session.openOutputStream()) {
+                out.write(content.getBytes(StandardCharsets.UTF_8));
+            }
+            return session.publish();
+        }
+    }
+
+    private static Uri createFile() throws IOException {
+        return createSubtitle();
+    }
+
     private static Uri createAudio() throws IOException {
         final Context context = InstrumentationRegistry.getTargetContext();
         final String displayName = "cts" + System.nanoTime();
         final PendingParams params = new PendingParams(
                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, displayName, "audio/mpeg");
         final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+
         try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
             try (InputStream in = context.getResources().getAssets().open("testmp3.mp3");
                     OutputStream out = session.openOutputStream()) {
@@ -843,7 +1116,7 @@
         final Context context = InstrumentationRegistry.getTargetContext();
         final String displayName = "cts" + System.nanoTime();
         final PendingParams params = new PendingParams(
-                MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), displayName,
+                MediaStore.Files.getContentUri(VOLUME_EXTERNAL), displayName,
                 "application/x-subrip");
         final Uri pendingUri = MediaStoreUtils.createPending(context, params);
         try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
@@ -865,6 +1138,18 @@
         }
     }
 
+    private static void clearOwnFiles(Uri uri) throws Exception {
+        final ContentResolver resolver = InstrumentationRegistry.getTargetContext()
+                .getContentResolver();
+        try (Cursor c = resolver.query(uri, new String[]{MediaColumns._ID}, null, null)) {
+            while(c.moveToNext()) {
+                final long id = c.getLong(0);
+                final Uri contentUri = ContentUris.withAppendedId(uri, id);
+                resolver.delete(contentUri, null);
+            }
+        }
+    }
+
     private static void clearMediaOwner(Uri uri, int userId) throws IOException {
         final String cmd = String.format(
                 "content update --uri %s --user %d --bind owner_package_name:n:",
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp
new file mode 100644
index 0000000..146524c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/Android.bp
@@ -0,0 +1,33 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsNoAppDataStorageApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "truth-prebuilt",
+    ],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/AndroidManifest.xml
new file mode 100644
index 0000000..ea164a1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/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="com.android.cts.noappstorage">
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.noappstorage"
+                     android:label="Tests for app without data storage" />
+
+    <application>
+        <property android:name="android.internal.PROPERTY_NO_APP_DATA_STORAGE"
+                  android:value="true" />
+        <uses-library android:name="android.test.runner"/>
+    </application>
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS
new file mode 100644
index 0000000..601d04b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/OWNERS
@@ -0,0 +1,2 @@
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
+ioffe@google.com
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
new file mode 100644
index 0000000..e69ef3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/NoDataStorageApp/src/com/android/cts/noappstorage/NoAppDataStorageTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.noappstorage;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+
+/**
+ * Tests that exercise behaviour of an app without access to apps' data storage.
+ */
+// TODO(b/211761016): add tests for external storage.
+@RunWith(JUnit4.class)
+public class NoAppDataStorageTest {
+
+    private final Context mCeContext = getInstrumentation().getContext();
+    private final Context mDeContext = mCeContext.createDeviceProtectedStorageContext();
+
+    @Test
+    public void testNoInternalCeStorage() throws Exception {
+        assertDirDoesNotExist(mCeContext.getDataDir());
+        assertDirDoesNotExist(mCeContext.getFilesDir());
+        assertDirDoesNotExist(mCeContext.getCacheDir());
+        assertDirDoesNotExist(mCeContext.getCodeCacheDir());
+    }
+
+    @Test
+    public void testNoInternalDeStorage() throws Exception {
+        assertDirDoesNotExist(mDeContext.getDataDir());
+        assertDirDoesNotExist(mDeContext.getFilesDir());
+        assertDirDoesNotExist(mDeContext.getCacheDir());
+        assertDirDoesNotExist(mDeContext.getCodeCacheDir());
+    }
+
+    private void assertDirDoesNotExist(File dir) throws Exception {
+        assertThat(dir.exists()).isFalse();
+        assertThat(dir.mkdirs()).isFalse();
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
index f375fa5..0af7515 100644
--- a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/Android.bp
@@ -35,6 +35,7 @@
         "general-tests",
     ],
     manifest: "AndroidManifest.xml",
+    sdk_version: "current",
 }
 
 android_test_helper_app {
@@ -56,6 +57,7 @@
         "general-tests",
     ],
     manifest: "AndroidManifestTestOnly.xml",
+    sdk_version: "current",
 }
 
 android_test_helper_app {
@@ -77,6 +79,7 @@
         "general-tests",
     ],
     manifest: "AndroidManifestTargetQ.xml",
+    sdk_version: "current",
 }
 
 android_test_helper_app {
@@ -98,6 +101,7 @@
         "general-tests",
     ],
     manifest: "AndroidManifestTargetR.xml",
+    sdk_version: "current",
 }
 
 android_test_helper_app {
@@ -119,4 +123,5 @@
         "general-tests",
     ],
     manifest: "AndroidManifestTargetS.xml",
-}
\ No newline at end of file
+    sdk_version: "current",
+}
diff --git a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
index e6e2ced..fdb701d 100644
--- a/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadSettingsFieldsApp/src/com/android/cts/readsettingsfieldsapp/ReadSettingsFieldsTest.java
@@ -27,7 +27,6 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.util.Arrays;
 import java.util.ArrayList;
 
 public class ReadSettingsFieldsTest extends AndroidTestCase {
@@ -53,7 +52,7 @@
                 if (isSettingsDeprecated(ex)) {
                     continue;
                 }
-                fail("Reading public " + settingsClass.getSimpleName() + " settings key <" + key
+                fail("Reading non-hidden " + settingsClass.getSimpleName() + " settings key <" + key
                         + "> should not raise exception! "
                         + "Did you forget to add @Readable annotation?\n" + ex.getMessage());
             }
@@ -220,6 +219,29 @@
                 hiddenSettingsKeys);
     }
 
+
+    public void testSettingsKeysNotReadableForAfterR() {
+        final String keyWithTargetSdkR = "media_button_receiver";
+        try {
+            // Verify that the hidden key is not readable because of maxTargetSdk restriction
+            callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
+            fail("Reading hidden settings key <" + keyWithTargetSdkR
+                    + "> should raise!");
+        } catch (SecurityException ex) {
+            assertTrue(ex.getMessage().contains("targetSdkVersion"));
+        }
+    }
+
+    public void testSettingsKeysReadableForRMinus() {
+        final String keyWithTargetSdkR = "media_button_receiver";
+        try {
+            // Verify that the hidden key can still be read
+            callGetStringMethod(Settings.System.class, keyWithTargetSdkR);
+        } catch (SecurityException ex) {
+            fail("Reading hidden settings key <" + keyWithTargetSdkR + "> should not raise!");
+        }
+    }
+
     public void testQueryGlobalSettingsNoHiddenKeysWithoutAnnotation() {
         checkQueryResults(Settings.Global.CONTENT_URI, Settings.Global.class);
     }
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
index c42fd09..749de46 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.bp
@@ -67,35 +67,6 @@
     ],
 }
 
-android_test_helper_app {
-    name: "CtsSplitApp29",
-    defaults: ["CtsSplitAppDefaults"],
-    package_splits: [
-        "mdpi-v4",
-        "hdpi-v4",
-        "xhdpi-v4",
-        "xxhdpi-v4",
-        "v7",
-        "v23",
-        "fr",
-        "de",
-    ],
-    certificate: ":cts-testkey1",
-    aaptflags: [
-        "--version-code 100",
-        "--version-name OneHundred",
-        "--replace-version",
-    ],
-    // Feature splits are dependent on this base, so it must be exported.
-    export_package_resources: true,
-    test_suites: [
-        "cts",
-        "general-tests",
-        "mts-mainline-infra",
-    ],
-    target_sdk_version: "29"
-}
-
 // Define a variant with a different revision code
 android_test_helper_app {
     name: "CtsSplitAppDiffRevision",
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp
new file mode 100644
index 0000000..e8656a1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/Android.bp
@@ -0,0 +1,76 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Define a variant requiring two split types for install
+android_test_helper_app {
+    name: "CtsRequiredSplitTypeSplitApp",
+    manifest: "AndroidManifest.xml",
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--version-name OneHundredRevisionTwelve",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant requiring three split types for install
+android_test_helper_app {
+    name: "CtsRequiredSplitTypeSplitAppUpdated",
+    manifest: "AndroidManifest_updated.xml",
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+// Define a variant having invalid required split types
+android_test_helper_app {
+    name: "CtsInvalidRequiredSplitTypeSplitApp",
+    manifest: "AndroidManifest_bad.xml",
+    sdk_version: "current",
+    min_sdk_version: "4",
+    aapt_include_all_resources: true,
+    certificate: ":cts-testkey1",
+    aaptflags: [
+        "--version-code 100",
+        "--revision-code 10",
+        "--version-name OneHundredRevisionTen",
+        "--replace-version",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml
new file mode 100644
index 0000000..907691c
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.splitapp"
+          android:targetSandboxVersion="2"
+          android:requiredSplitTypes="density,locale">
+
+    <uses-sdk android:minSdkVersion="4"
+              android:targetSdkVersion="27"/>
+
+    <application android:label="SplitApp">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml
new file mode 100644
index 0000000..3e7a1b4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_bad.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.splitapp"
+          android:targetSandboxVersion="2"
+          android:requiredSplitTypes="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789">
+
+    <uses-sdk android:minSdkVersion="4"
+              android:targetSdkVersion="27"/>
+
+    <application android:label="SplitApp">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml
new file mode 100644
index 0000000..cdff5b5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/AndroidManifest_updated.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.splitapp"
+          android:targetSandboxVersion="2"
+          android:requiredSplitTypes="density,locale,feature">
+
+    <uses-sdk android:minSdkVersion="4"
+              android:targetSdkVersion="27"/>
+
+    <application android:label="SplitApp">
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.splitapp"/>
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp
new file mode 100644
index 0000000..ac1fbe8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/Android.bp
@@ -0,0 +1,101 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeDensity",
+    manifest: "density/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeLocale",
+    manifest: "locale/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeMultiple",
+    manifest: "multitype/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeFoo",
+    manifest: "foo/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeFeature",
+    manifest: "feature/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeFeatureData",
+    manifest: "feature_data/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSplitAppTypeFeatureFoo",
+    manifest: "feature_foo/AndroidManifest.xml",
+    certificate: ":cts-testkey1",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml
new file mode 100644
index 0000000..9e50b38
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/density/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="config.xxhdpi"
+    targetConfig="xxhdpi"
+    android:splitTypes="density">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/AndroidManifest.xml
new file mode 100644
index 0000000..d681953
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature/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"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    android:isFeatureSplit="true"
+    split="feature_foo"
+    android:splitTypes="feature"
+    android:requiredSplitTypes="feature.data">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml
new file mode 100644
index 0000000..496ef5f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_data/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="feature_foo.data"
+    android:splitTypes="feature.data">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml
new file mode 100644
index 0000000..80f06b0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/feature_foo/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="feature_foo.foo"
+    android:splitTypes="feature.foo">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml
new file mode 100644
index 0000000..e514d9b
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/foo/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="config.foo"
+    android:splitTypes="foo">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml
new file mode 100644
index 0000000..372bbb0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/locale/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="config.de"
+    android:splitTypes="locale">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml
new file mode 100644
index 0000000..d640edb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/requiredsplittype/types/multitype/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?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"
+    android:versionCode="100"
+    package="com.android.cts.splitapp"
+    split="config.split"
+    android:splitTypes="locale,density">
+
+    <application
+        android:hasCode="false" />
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
index 2f06395..d617251 100644
--- a/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
+++ b/hostsidetests/appsecurity/test-apps/V3SigningSchemeRotation/src/android/appsecurity/cts/v3rotationtests/V3RotationTest.java
@@ -251,6 +251,36 @@
                 pm.hasSigningCertificate(uid, secondCertBytes, PackageManager.CERT_INPUT_SHA256));
     }
 
+    public void testUsingOriginalSigner() throws Exception {
+        // Verifies the platform only recognized the original signing key during installation.
+        PackageManager pm = getContext().getPackageManager();
+        PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
+        assertFalse(
+                "APK is expected to use the original signing key, but past signing certificates "
+                        + "were reported",
+                pi.signingInfo.hasPastSigningCertificates());
+        assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_FIRST_CERT_HEX);
+        byte[] secondCertBytes = fromHexToByteArray(EC_P256_SECOND_CERT_HEX);
+        assertFalse("APK is not expected to have the rotated key in its signing lineage",
+                pm.hasSigningCertificate(PKG, secondCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+    }
+
+    public void testUsingRotatedSigner() throws Exception {
+        // Verifies the platform recognized the rotated signing key during installation.
+        PackageManager pm = getContext().getPackageManager();
+        PackageInfo pi = pm.getPackageInfo(PKG, PackageManager.GET_SIGNING_CERTIFICATES);
+        assertTrue(
+                "APK is expected to be signed with the rotated signing key, but past signing "
+                        + "certificates were not reported",
+                pi.signingInfo.hasPastSigningCertificates());
+        assertExpectedSignatures(pi.signingInfo.getApkContentsSigners(), EC_P256_SECOND_CERT_HEX);
+        assertExpectedSignatures(pi.signingInfo.getSigningCertificateHistory(),
+                EC_P256_FIRST_CERT_HEX, EC_P256_SECOND_CERT_HEX);
+        byte[] firstCertBytes = fromHexToByteArray(EC_P256_FIRST_CERT_HEX);
+        assertTrue("APK is expected to have the original key in its signing lineage",
+                pm.hasSigningCertificate(PKG, firstCertBytes, PackageManager.CERT_INPUT_RAW_X509));
+    }
+
     private  static byte[] fromHexToByteArray(String str) {
         if (str == null || str.length() == 0 || str.length() % 2 != 0) {
             return null;
diff --git a/hostsidetests/appsecurity/test-apps/rro/OWNERS b/hostsidetests/appsecurity/test-apps/rro/OWNERS
index 21cd9d9..870d1a4 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OWNERS
+++ b/hostsidetests/appsecurity/test-apps/rro/OWNERS
@@ -1,2 +1,2 @@
 # Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
index fbc77e1..607f13f 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/Android.bp
@@ -21,8 +21,16 @@
 android_test_helper_app {
     name: "CtsOverlayTarget",
     defaults: ["cts_support_defaults"],
-    sdk_version: "current",
+    sdk_version: "test_current",
     certificate: ":cts-testkey1",
+    static_libs: [
+        "truth-prebuilt",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+    ],
+    srcs: [
+        "src/**/*.java",
+    ],
     resource_dirs: [
         "res",
         "res_overlayable",
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
index a0d609a..03e4f7d 100644
--- a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/AndroidManifest.xml
@@ -16,4 +16,13 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.overlay.target">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".OverlayTargetActivity" android:exported="false"
+                  android:configChanges="@integer/config_changes_assets_paths" />
+        <service android:name=".OverlayTargetService" android:exported="false" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.overlay.target" />
 </manifest>
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml
new file mode 100644
index 0000000..67c0149
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/res/values/integer.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<resources>
+    <!-- The integer to indicate that activity handles assets paths changes itself -->
+    <integer name="config_changes_assets_paths">0x80000000</integer>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java
new file mode 100644
index 0000000..d94d511
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetActivity.java
@@ -0,0 +1,90 @@
+/*
+ * 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.overlay.target;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.function.BiConsumer;
+
+/**
+ * A test activity to verify that the assets paths configuration changes are received if the
+ * overlay targeting state is changed.
+ */
+public class OverlayTargetActivity extends Activity {
+    private BiConsumer<OverlayTargetActivity, Configuration> mConfigurationChangedCallback;
+
+    /**
+     * A boolean value to determine whether a stub service can be started when the activity
+     * is launched.
+     */
+    public static final String EXTRA_START_SERVICE =
+            "com.android.cts.overlay.intent.extra.START_SERVICE";
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        keepScreenOn();
+        if (savedInstanceState == null) {
+            startServiceIfNecessary(getIntent());
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        final BiConsumer<OverlayTargetActivity, Configuration> callback =
+                mConfigurationChangedCallback;
+        if (callback != null) {
+            callback.accept(this, newConfig);
+        }
+    }
+
+    /** Registers the callback of onConfigurationChanged. */
+    public void setConfigurationChangedCallback(
+            BiConsumer<OverlayTargetActivity, Configuration> callbacks) {
+        mConfigurationChangedCallback = callbacks;
+    }
+
+    private void keepScreenOn() {
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        setTurnScreenOn(true);
+        KeyguardManager km = getSystemService(KeyguardManager.class);
+        if (km != null) {
+            km.requestDismissKeyguard(this, null);
+        }
+    }
+
+    private void startServiceIfNecessary(Intent intent) {
+        if (intent == null) {
+            return;
+        }
+        final boolean startService = intent.getBooleanExtra(EXTRA_START_SERVICE, false);
+        if (!startService) {
+            return;
+        }
+        final Intent serviceIntent = new Intent(this, OverlayTargetService.class);
+        startService(serviceIntent);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java
new file mode 100644
index 0000000..3fb4fa0
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetService.java
@@ -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.
+ */
+
+package com.android.cts.overlay.target;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+/** A stub service used by {@link OverlayTargetActivity} */
+public class OverlayTargetService extends Service {
+    private Binder mBinder = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java
new file mode 100644
index 0000000..938f1c4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/rro/OverlayTarget/src/com/android/cts/overlay/target/OverlayTargetTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.overlay.target;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class OverlayTargetTest {
+    // overlay package
+    private static final String OVERLAY_ALL_PACKAGE_NAME = "com.android.cts.overlay.all";
+
+    // Overlay states
+    private static final String STATE_DISABLED = "STATE_DISABLED";
+    private static final String STATE_ENABLED = "STATE_ENABLED";
+
+    // Default timeout value
+    private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
+
+    // Keys for test arguments
+    private static final String PARAM_START_SERVICE = "start_service";
+
+    private Instrumentation mInstrumentation;
+
+    @Rule
+    public ActivityTestRule<OverlayTargetActivity> mActivityTestRule = new ActivityTestRule<>(
+            OverlayTargetActivity.class, false /* initialTouchMode */, false /* launchActivity */);
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        launchOverlayTargetActivity(InstrumentationRegistry.getArguments());
+        assertThat(mActivityTestRule.getActivity()).isNotNull();
+    }
+
+    @Test
+    public void overlayEnabled_activityInForeground() throws Exception {
+        final OverlayTargetActivity targetActivity = mActivityTestRule.getActivity();
+        final CountDownLatch latch = new CountDownLatch(1);
+        targetActivity.setConfigurationChangedCallback((activity, config) -> {
+            latch.countDown();
+            activity.setConfigurationChangedCallback(null);
+        });
+
+        setOverlayEnabled(OVERLAY_ALL_PACKAGE_NAME, true /* enabled */);
+
+        if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Fail to wait configuration changes for the overlay target activity.");
+        }
+    }
+
+    @Test
+    public void overlayEnabled_activityInBackground_toForeground() throws Exception {
+        final OverlayTargetActivity targetActivity = mActivityTestRule.getActivity();
+        // Activity goes into background
+        launchHome();
+        mInstrumentation.waitForIdleSync();
+        final CountDownLatch latch = new CountDownLatch(1);
+        targetActivity.setConfigurationChangedCallback((activity, config) -> {
+            latch.countDown();
+            activity.setConfigurationChangedCallback(null);
+        });
+        setOverlayEnabled(OVERLAY_ALL_PACKAGE_NAME, true /* enabled */);
+
+        if (latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity in background should not receive configuration changes");
+        }
+
+        // Bring activity to foreground
+        final Intent intent = new Intent(mInstrumentation.getTargetContext(),
+                OverlayTargetActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+        targetActivity.startActivity(intent);
+
+        if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Fail to wait configuration changes for the overlay target activity.");
+        }
+    }
+
+    private void launchOverlayTargetActivity(Bundle testArgs) {
+        final Intent intent = new Intent(mInstrumentation.getTargetContext(),
+                OverlayTargetActivity.class);
+        final boolean startService = (testArgs != null
+                && "true".equalsIgnoreCase(testArgs.getString(PARAM_START_SERVICE)));
+        intent.putExtra(OverlayTargetActivity.EXTRA_START_SERVICE, startService);
+        mActivityTestRule.launchActivity(intent);
+        mInstrumentation.waitForIdleSync();
+    }
+
+    private static void setOverlayEnabled(String overlayPackage, boolean enabled)
+            throws Exception {
+        final String current = getStateForOverlay(overlayPackage);
+        final String expected = enabled ? STATE_ENABLED : STATE_DISABLED;
+        assertThat(current).isNotEqualTo(expected);
+        SystemUtil.runShellCommand("cmd overlay "
+                + (enabled ? "enable" : "disable")
+                + " --user current "
+                + overlayPackage);
+        PollingCheck.check("Fail to wait overlay enabled state " + expected
+                        + " for " + overlayPackage, TIMEOUT_MS,
+                () -> expected.equals(getStateForOverlay(overlayPackage)));
+    }
+
+    private static void launchHome() {
+        SystemUtil.runShellCommand("am start -W -a android.intent.action.MAIN"
+                + " -c android.intent.category.HOME");
+    }
+
+    private static String getStateForOverlay(String overlayPackage) {
+        final String errorMsg = "Fail to parse the state of overlay package " + overlayPackage;
+        final String result = SystemUtil.runShellCommand("cmd overlay dump");
+        final String overlayPackageForCurrentUser = overlayPackage + ":" + UserHandle.myUserId();
+        final int startIndex = result.indexOf(overlayPackageForCurrentUser);
+        assertWithMessage(errorMsg).that(startIndex).isAtLeast(0);
+
+        final int endIndex = result.indexOf('}', startIndex);
+        assertWithMessage(errorMsg).that(endIndex).isGreaterThan(startIndex);
+
+        final int stateIndex = result.indexOf("mState", startIndex);
+        assertWithMessage(errorMsg).that(startIndex).isLessThan(stateIndex);
+        assertWithMessage(errorMsg).that(stateIndex).isLessThan(endIndex);
+
+        final int colonIndex = result.indexOf(':', stateIndex);
+        assertWithMessage(errorMsg).that(stateIndex).isLessThan(colonIndex);
+        assertWithMessage(errorMsg).that(colonIndex).isLessThan(endIndex);
+
+        final int endLineIndex = result.indexOf('\n', colonIndex);
+        assertWithMessage(errorMsg).that(colonIndex).isLessThan(endLineIndex);
+        assertWithMessage(errorMsg).that(endLineIndex).isLessThan(endIndex);
+
+        return result.substring(colonIndex + 2, endLineIndex);
+    }
+}
diff --git a/hostsidetests/backup/Android.bp b/hostsidetests/backup/Android.bp
index c60681c..b75c8a2 100644
--- a/hostsidetests/backup/Android.bp
+++ b/hostsidetests/backup/Android.bp
@@ -21,7 +21,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
         "mts",
diff --git a/hostsidetests/backup/AutoRestoreApp/Android.bp b/hostsidetests/backup/AutoRestoreApp/Android.bp
index 58b58dd..fbab3cb 100644
--- a/hostsidetests/backup/AutoRestoreApp/Android.bp
+++ b/hostsidetests/backup/AutoRestoreApp/Android.bp
@@ -29,7 +29,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/BackupTransportApp/Android.bp b/hostsidetests/backup/BackupTransportApp/Android.bp
index 7b07cbe..7a4484f 100644
--- a/hostsidetests/backup/BackupTransportApp/Android.bp
+++ b/hostsidetests/backup/BackupTransportApp/Android.bp
@@ -28,7 +28,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/fullbackupapp/Android.bp b/hostsidetests/backup/fullbackupapp/Android.bp
index a7d81d6..ec5e274 100644
--- a/hostsidetests/backup/fullbackupapp/Android.bp
+++ b/hostsidetests/backup/fullbackupapp/Android.bp
@@ -26,7 +26,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
index 6a4a9b3..ae56169 100644
--- a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApp/Android.bp
@@ -27,7 +27,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
index e2ae477..9175932 100644
--- a/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/DataExtractionRulesApplicabilityApp/Android.bp
@@ -27,7 +27,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp b/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
index fd106e9..9cfb39a 100644
--- a/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/EncryptionAttributeApp/Android.bp
@@ -27,7 +27,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp b/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
index cb2fb1e..378c786 100644
--- a/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
+++ b/hostsidetests/backup/includeexcludeapp/FullBackupContentApp/Android.bp
@@ -27,7 +27,6 @@
     srcs: ["src/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
index 6928718..fc355af 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BaseBlobStoreHostTest.java
@@ -98,10 +98,6 @@
         return device.isMultiUserSupported();
     }
 
-    protected boolean isMultiUserSupported() throws Exception {
-        return isMultiUserSupported(getDevice());
-    }
-
     protected Map<String, String> createArgsFromLastTestRun() {
         final Map<String, String> args = new HashMap<>();
         for (String key : new String[] {
diff --git a/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java b/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
index 9a78386..ec13654 100644
--- a/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
+++ b/hostsidetests/blobstore/src/com/android/cts/host/blob/BlobStoreMultiUserTest.java
@@ -40,9 +40,9 @@
 
     @BeforeClassWithInfo
     public static void setUpClass(TestInformation testInfo) throws Exception {
-        if(!isMultiUserSupported(testInfo.getDevice())) {
-            return;
-        }
+        assumeTrue("Multi-user is not supported on this device",
+                isMultiUserSupported(testInfo.getDevice()));
+
         mPrimaryUserId = testInfo.getDevice().getPrimaryUserId();
         mSecondaryUserId = testInfo.getDevice().createUser("Test_User");
         assertThat(testInfo.getDevice().startUser(mSecondaryUserId)).isTrue();
@@ -50,8 +50,6 @@
 
     @Before
     public void setUp() throws Exception {
-        assumeTrue("Multi-user is not supported on this device", isMultiUserSupported());
-
         for (String apk : new String[] {TARGET_APK, TARGET_APK_DEV}) {
             installPackageAsUser(apk, true /* grantPermissions */, mPrimaryUserId, "-t");
             installPackageAsUser(apk, true /* grantPermissions */, mSecondaryUserId, "-t");
diff --git a/hostsidetests/car/app/Android.bp b/hostsidetests/car/app/Android.bp
index 32e33d8..144028d 100644
--- a/hostsidetests/car/app/Android.bp
+++ b/hostsidetests/car/app/Android.bp
@@ -30,7 +30,7 @@
         "compatibility-device-util-axt",
     ],
 
-    libs: ["android.car"],
+    libs: ["android.car-test-stubs"],
 }
 
 android_test_helper_app {
diff --git a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
index 7057462..66c6c5a 100644
--- a/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
+++ b/hostsidetests/car/app/src/android/car/cts/app/CarWatchdogTestActivity.java
@@ -299,6 +299,7 @@
                     CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
                     CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
         }
+        Log.d(TAG, "Fetched resource overuse stats: " + stats);
         IoOveruseStats ioOveruseStats = stats.getIoOveruseStats();
         if (ioOveruseStats == null) {
             setDumpMessage(
@@ -312,7 +313,6 @@
                     + "' returned by get request");
             return 0;
         }
-        Log.d(TAG, ioOveruseStats.toString());
         /*
          * Check for foreground mode bytes given CtsCarApp is running in the foreground
          * during testing.
@@ -343,26 +343,24 @@
         @Override
         public void onOveruse(ResourceOveruseStats resourceOveruseStats) {
             synchronized (mLock) {
+                Log.d(TAG, "onOveruse callback received: " + resourceOveruseStats);
                 mForegroundModeBytes = -1;
                 mNotificationReceived = true;
                 mLock.notifyAll();
-            }
-            Log.d(TAG, resourceOveruseStats.toString());
-            if (resourceOveruseStats.getIoOveruseStats() == null) {
-                setDumpMessage(
-                        "ERROR: No I/O overuse stats reported for the application in the overuse "
-                        + "notification.");
-                return;
-            }
-            long reportedWrittenBytes =
-                    resourceOveruseStats.getIoOveruseStats().getTotalBytesWritten();
-            if (reportedWrittenBytes < mExpectedMinWrittenBytes) {
-                setDumpMessage("ERROR: Actual written bytes to disk '" + mExpectedMinWrittenBytes
-                        + "' don't match written bytes '" + reportedWrittenBytes
-                        + "' reported in overuse notification");
-                return;
-            }
-            synchronized (mLock) {
+                if (resourceOveruseStats.getIoOveruseStats() == null) {
+                    setDumpMessage(
+                            "ERROR: No I/O overuse stats reported for the application in the "
+                            + "overuse notification.");
+                    return;
+                }
+                long reportedWrittenBytes =
+                        resourceOveruseStats.getIoOveruseStats().getTotalBytesWritten();
+                if (reportedWrittenBytes < mExpectedMinWrittenBytes) {
+                    setDumpMessage("ERROR: Actual written bytes to disk '"
+                            + mExpectedMinWrittenBytes + "' don't match written bytes '"
+                            + reportedWrittenBytes + "' reported in overuse notification");
+                    return;
+                }
                 mForegroundModeBytes =
                         resourceOveruseStats.getIoOveruseStats().getRemainingWriteBytes()
                                 .getForegroundModeBytes();
diff --git a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
index 595881a..be1aac1 100644
--- a/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
+++ b/hostsidetests/car/src/android/car/cts/CarHostJUnit4TestCase.java
@@ -270,14 +270,14 @@
      * Creates a full user with car service shell command.
      */
     protected int createFullUser(String name) throws Exception {
-        return createUser(name, /* flags= */ 0, "android.os.usertype.full.SECONDARY");
+        return createUser(name, /* flags= */ 0, /* isGuest= */ false);
     }
 
     /**
      * Creates a full guest with car service shell command.
      */
     protected int createGuestUser(String name) throws Exception {
-        return createUser(name, /* flags= */ 0, "android.os.usertype.full.GUEST");
+        return createUser(name, /* flags= */ 0, /* isGuest= */ true);
     }
 
     /**
@@ -285,14 +285,15 @@
      *
      * <p><b>NOTE: </b>it uses User HAL flags, not core Android's.
      */
-    protected int createUser(String name, int flags, String type) throws Exception {
+    protected int createUser(String name, int flags, boolean isGuest) throws Exception {
         name = USER_PREFIX + "." + name;
         waitForCarServiceReady();
         int userId = executeAndParseCommand(CREATE_USER_OUTPUT_PATTERN,
-                "Could not create user with name " + name + ", flags " + flags + ", type" + type,
+                "Could not create user with name " + name
+                        + ", flags " + flags + ", guest " + isGuest,
                 matcher -> Integer.parseInt(matcher.group(1)),
-                "cmd car_service create-user --flags %d --type %s %s",
-                flags, type, name);
+                "cmd car_service create-user --flags %d %s%s",
+                flags, (isGuest ? "--guest " : ""), name);
         markUserForRemovalAfterTest(userId);
         return userId;
     }
diff --git a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
index f66b391..014c783 100644
--- a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
@@ -29,6 +29,7 @@
 import com.android.os.AtomsProto.CarWatchdogIoOveruseStatsReported;
 import com.android.os.AtomsProto.CarWatchdogKillStatsReported;
 import com.android.os.StatsLog.EventMetricData;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.After;
@@ -36,6 +37,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.LocalDateTime;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.atomic.AtomicReference;
@@ -44,6 +46,8 @@
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CarWatchdogHostTest extends CarHostJUnit4TestCase {
+    public static final String TAG = CarWatchdogHostTest.class.getSimpleName();
+
     /**
      * CarWatchdog app package.
      */
@@ -120,9 +124,20 @@
 
     private static final long WATCHDOG_ACTION_TIMEOUT_MS = 15_000;
 
+    private boolean mDidModifyDateTime;
     private long mOriginalForegroundBytes;
 
     @Before
+    public void dateSetUp() throws Exception {
+        checkAndSetDate();
+    }
+
+    @After
+    public void dateReset() throws Exception {
+        checkAndResetDate();
+    }
+
+    @Before
     public void setUp() throws Exception {
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
@@ -328,4 +343,24 @@
                 "am start -W -a android.intent.action.MAIN -n %s/%s --el bytes_to_kill %d",
                 appPkg, ACTIVITY_CLASS, remainingBytes);
     }
+
+    private void checkAndSetDate() throws Exception {
+        // Get date in ISO-8601 format
+        LocalDateTime now = LocalDateTime.parse(executeCommand("date +%%FT%%T").trim());
+        if (now.getHour() < 23) {
+            return;
+        }
+        executeCommand("date %s", now.minusHours(1));
+        CLog.d(TAG, "DateTime changed from %s to %s", now, now.minusHours(1));
+        mDidModifyDateTime = true;
+    }
+
+    private void checkAndResetDate() throws Exception {
+        if (!mDidModifyDateTime) {
+            return;
+        }
+        LocalDateTime now = LocalDateTime.parse(executeCommand("date +%%FT%%T").trim());
+        executeCommand("date %s", now.plusHours(1));
+        CLog.d(TAG, "DateTime changed from %s to %s", now, now.plusHours(1));
+    }
 }
diff --git a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
index cc8823a..3eb85a9 100644
--- a/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
+++ b/hostsidetests/car/src/android/car/cts/GarageModeAtomTests.java
@@ -94,7 +94,7 @@
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getGarageModeInfo().getIsGarageMode() ?  1 : 0);
 
     }
diff --git a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
index 1d5ebcf..604adab 100644
--- a/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/PreCreateUsersHostTest.java
@@ -164,7 +164,7 @@
                 : createFullUser("PreCreatedUsersTest_Reference_User");
         // Some permissions (e.g. Role permission) are given only after initialization.
         switchUser(referenceUserId);
-        waitUntilUserPermissionsIsReady(referenceUserId);
+        waitUntilUserPermissionsIsReady(/* waitTime= */ 30, referenceUserId);
         Map<String, List<String>> refPkgMap = getPackagesAndPermissionsForUser(referenceUserId);
 
         // There can be just one guest by default, so remove it now otherwise
@@ -182,7 +182,7 @@
         convertPreCreatedUser(isGuest, preCreatedUserId);
         // Some permissions (e.g. Role permission) are given only after initialization.
         switchUser(preCreatedUserId);
-        waitUntilUserPermissionsIsReady(preCreatedUserId);
+        waitUntilUserPermissionsIsReady(/* waitTime= */ 20, preCreatedUserId);
         Map<String, List<String>> actualPkgMap = getPackagesAndPermissionsForUser(preCreatedUserId);
 
         List<String> errors = new ArrayList<>();
@@ -192,6 +192,20 @@
                             .that(actualPkgMap.get(pkg))
                             .isEqualTo(refPkgMap.get(pkg)));
         }
+
+        if (!errors.isEmpty()) {
+            // if there are errors, wait for some more time and check again.
+            waitUntilUserPermissionsIsReady(/* waitTime= */ 20, preCreatedUserId);
+            Map<String, List<String>> actualPkgMap2 = getPackagesAndPermissionsForUser(
+                    preCreatedUserId);
+
+            errors = new ArrayList<>();
+            for (String pkg : refPkgMap.keySet()) {
+                addError(errors, () -> assertWithMessage("permissions state mismatch for %s", pkg)
+                        .that(actualPkgMap2.get(pkg))
+                        .isEqualTo(refPkgMap.get(pkg)));
+            }
+        }
         assertWithMessage("found %s error", errors.size()).that(errors).isEmpty();
     }
 
@@ -246,10 +260,10 @@
     }
 
     // TODO(b/170263003): update this method after core framewokr's refactoring for proto
-    private void waitUntilUserPermissionsIsReady(int userId) throws InterruptedException {
-        int napTimeSec = 20;
-        CLog.i("Sleeping %ds to make permissions for user %d is ready", napTimeSec, userId);
-        sleep(napTimeSec * 1_000);
+    private void waitUntilUserPermissionsIsReady(int waitTime, int userId)
+            throws InterruptedException {
+        CLog.i("Sleeping %ds to make permissions for user %d is ready", waitTime, userId);
+        sleep(waitTime * 1_000);
     }
 
     private void deletePreCreatedUsers() throws Exception {
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
index d7f6108..08abe56 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/CpmsFrameworkLayerStateInfo.java
@@ -23,6 +23,8 @@
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 public final class CpmsFrameworkLayerStateInfo {
     private static final int STRING_BUILDER_BUF_SIZE = 1024;
@@ -302,12 +304,14 @@
             int val = 0;
             switch (header) {
                 case CURRENT_STATE_HDR:
-                    String[] tokens = mLines[mIdx].split(",*\\s");
-                    if (tokens.length != 6) {
+                    Pattern pattern = Pattern.compile("mCurrentState: CpmsState "
+                            + "[^\\n]*carPowerStateListenerState=(\\d+)");
+                    Matcher matcher = pattern.matcher(mLines[mIdx]);
+                    if (!matcher.find()) {
                         throw new IllegalArgumentException("malformatted mCurrentState: "
                                 + mLines[mIdx]);
                     }
-                    val = Integer.parseInt(tokens[4].trim().substring(tokens[4].length() - 1));
+                    val = Integer.parseInt(matcher.group(1));
                     break;
                 case NUMBER_POLICY_LISTENERS_HDR:
                     int strLen = mLines[mIdx].length();
diff --git a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
index f0fbbab..9bdd2ef 100644
--- a/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
+++ b/hostsidetests/car/src/android/car/cts/powerpolicy/PowerPolicyConstants.java
@@ -20,6 +20,8 @@
     public static final int VHAL_POWER_STATE_REQ_PROPERTY_ID = 289475072;
     public static final int VHAL_POWER_STATE_REP_PROPERTY_ID = 289475073;
 
+    private PowerPolicyConstants() { }
+
     public static final class CarPowerState {
         public static final int INVALID = 0;
         public static final int WAIT_FOR_VHAL = 1;
@@ -29,6 +31,8 @@
         public static final int ON = 6;
         public static final int SHUTDOWN_PREPARE = 7;
         public static final int SHUTDOWN_CANCELLED = 8;
+
+        private CarPowerState() { }
     }
 
     public static final class VhalPowerStateReq {
@@ -36,6 +40,8 @@
         public static final int SHUTDOWN_PREPARE = 1;
         public static final int CANCEL_SHUTDOWN = 2;
         public static final int FINISHED = 3;
+
+        private VhalPowerStateReq() { }
     }
 
     public static final class ShutdownParam {
@@ -44,5 +50,7 @@
         public static final int CAN_SLEEP = 2;
         public static final int SHUTDOWN_ONLY = 3;
         public static final int SLEEP_IMMEDIATELY = 4;
+
+        private ShutdownParam() { }
     }
 }
diff --git a/hostsidetests/car_builtin/Android.bp b/hostsidetests/car_builtin/Android.bp
new file mode 100644
index 0000000..fc8f7d9
--- /dev/null
+++ b/hostsidetests/car_builtin/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsCarBuiltinApiHostTestCases",
+    defaults: ["cts_defaults"],
+    // Only compile source java files in this apk.
+    srcs: [
+        "src/**/*.java",
+    ],
+    libs: [
+        "tradefed",
+        "compatibility-host-util",
+        "truth-prebuilt",
+    ],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+}
diff --git a/hostsidetests/car_builtin/AndroidTest.xml b/hostsidetests/car_builtin/AndroidTest.xml
new file mode 100644
index 0000000..a4fcf49
--- /dev/null
+++ b/hostsidetests/car_builtin/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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="Car Builtin APIs Host Tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="auto" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsCarBuiltinApiHostTestCases.jar" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/hostsidetests/car_builtin/OWNERS b/hostsidetests/car_builtin/OWNERS
new file mode 100644
index 0000000..f0afac5
--- /dev/null
+++ b/hostsidetests/car_builtin/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 526680
+keunyoung@google.com
+felipeal@google.com
+gurunagarajan@google.com
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java
new file mode 100644
index 0000000..b8aa638
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/BinderHelperHostTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin;
+
+import static android.car.cts.builtin.os.GetInitialUserInfoCommand.OK_STATUS_RETURN_HEADER;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.cts.builtin.os.GetInitialUserInfoCommand;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class BinderHelperHostTest extends BaseHostJUnit4Test {
+
+    // When a car shell command (such as, "cmd car_service get-do-activities") is called, it
+    // triggers both BinderHelper.onTransactForCmd and
+    // BinderHelper.ShellCommandListener.onShellCommand calls.
+    @Test
+    public void testOnTransactForCmd() throws Exception {
+        // setup
+        GetInitialUserInfoCommand infoCmd = new GetInitialUserInfoCommand(getDevice());
+
+        // execution and assertion
+        infoCmd.executeWith();
+        assertThat(infoCmd.returnStartsWith(OK_STATUS_RETURN_HEADER)).isTrue();
+    }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java
new file mode 100644
index 0000000..6ffaf40
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/CtsCarShellCommand.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin;
+
+import com.android.tradefed.device.ITestDevice;
+
+/**
+ * Base class for all Car Builtin API test related shell command invocation.
+ *
+ * It is the extended subclass command to construct the shell command string and decide
+ * if the command's return is successful or not. Further, it is the extended subclass command
+ * to extra all necessary information from the return string if needed.
+ */
+public abstract class CtsCarShellCommand {
+    private final ITestDevice mDevice;
+
+    protected final String mCommand;
+    protected String[] mCommandArgs;
+    protected String mCommandReturn;
+
+    protected CtsCarShellCommand(String commandName, ITestDevice device) {
+        mCommand = commandName;
+        mDevice = device;
+    }
+
+    public CtsCarShellCommand executeWith(String... args) throws Exception {
+        mCommandArgs = args;
+
+        String cmd = mCommand;
+        if (mCommandArgs != null && mCommandArgs.length > 0) {
+            cmd = mCommand + " " + String.join(" ", mCommandArgs);
+        }
+        mCommandReturn = mDevice.executeShellCommand(cmd);
+        parseCommandReturn();
+        return this;
+    }
+
+    public boolean returnStartsWith(String str) throws Exception {
+        if (mCommandReturn == null) {
+            throw new Exception("command return is null. not executed?");
+        }
+        return mCommandReturn.startsWith(str);
+    }
+
+    protected abstract void parseCommandReturn() throws Exception;
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java
new file mode 100644
index 0000000..e597c65
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/LockPatternHelperHostTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.cts.builtin.widget.CheckLockIsSecureCommand;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public final class LockPatternHelperHostTest extends BaseHostJUnit4Test {
+
+    @Test
+    public void testIsSecureApi() throws Exception {
+        // setup
+        CheckLockIsSecureCommand checkLockCmd = new CheckLockIsSecureCommand(getDevice());
+        String userId = String.valueOf(getDevice().getCurrentUser());
+
+        // execution and assertion
+        checkLockCmd.executeWith(userId);
+        // as the current user does not have any credentials, expect to be false
+        assertThat(checkLockCmd.isSecure()).isFalse();
+    }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java
new file mode 100644
index 0000000..f26d59b
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/os/GetInitialUserInfoCommand.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin.os;
+
+import android.car.cts.builtin.CtsCarShellCommand;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class GetInitialUserInfoCommand extends CtsCarShellCommand {
+
+    public static final String OK_STATUS_RETURN_HEADER = "Call status: OK";
+    private static final String COMMAND_NAME = "cmd car_service get-initial-user-info FIRST_BOOT";
+
+    public GetInitialUserInfoCommand(ITestDevice device) {
+        super(COMMAND_NAME, device);
+    }
+
+    @Override
+    protected void parseCommandReturn() throws Exception {
+        if (mCommandArgs.length != 0) {
+            throw new IllegalArgumentException("No argument is expected");
+        }
+        CLog.d(mCommand + " command returns: " + mCommandReturn);
+    }
+}
diff --git a/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java b/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java
new file mode 100644
index 0000000..722acff
--- /dev/null
+++ b/hostsidetests/car_builtin/src/android/car/cts/builtin/widget/CheckLockIsSecureCommand.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin.widget;
+
+import android.car.cts.builtin.CtsCarShellCommand;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+
+public final class CheckLockIsSecureCommand extends CtsCarShellCommand {
+    public static final String COMMAND_NAME = "cmd car_service check-lock-is-secure";
+
+    private boolean mIsSecure = false;
+
+    public CheckLockIsSecureCommand(ITestDevice device) {
+        super(COMMAND_NAME, device);
+    }
+
+    public boolean isSecure() {
+        return mIsSecure;
+    }
+
+    @Override
+    protected void parseCommandReturn() throws Exception {
+        if (mCommandArgs.length != 1) {
+            throw new IllegalArgumentException("user id is expected");
+        }
+
+        mIsSecure = Boolean.parseBoolean(mCommandReturn.trim());
+
+        CLog.d(mCommand + " command returns: " + mCommandReturn);
+        CLog.d("CheckLockIsSecureCommand.mIsSecure: " + mIsSecure);
+    }
+}
diff --git a/hostsidetests/compilation/Android.bp b/hostsidetests/compilation/Android.bp
index 6f4441f..e6ad6a2 100644
--- a/hostsidetests/compilation/Android.bp
+++ b/hostsidetests/compilation/Android.bp
@@ -31,5 +31,6 @@
         "tradefed",
         "compatibility-host-util",
         "guava",
+        "truth-prebuilt",
     ],
 }
diff --git a/hostsidetests/compilation/AndroidTest.xml b/hostsidetests/compilation/AndroidTest.xml
index da1e27d..76f23e5 100644
--- a/hostsidetests/compilation/AndroidTest.xml
+++ b/hostsidetests/compilation/AndroidTest.xml
@@ -22,6 +22,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
         <option name="jar" value="CtsCompilationTestCases.jar" />
-        <option name="runtime-hint" value="9m45s" />
+        <option name="runtime-hint" value="14m" />
     </test>
 </configuration>
diff --git a/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java b/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java
new file mode 100644
index 0000000..edcfaa4
--- /dev/null
+++ b/hostsidetests/compilation/src/android/compilation/cts/BackgroundDexOptimizationTest.java
@@ -0,0 +1,332 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.compilation.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import com.google.common.io.ByteStreams;
+
+import junit.framework.Assert;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Tests background dex optimization which runs as idle job.
+ */
+public final class BackgroundDexOptimizationTest extends DeviceTestCase {
+    private static final long REBOOT_TIMEOUT_MS = 600_000;
+    private static final long DEXOPT_TIMEOUT_MS = 1_200_000;
+    // Cancel should be faster. It will be usually much shorter but we cannot make it too short
+    // as CTS cannot enforce unspecified performance.
+    private static final long DEXOPT_CANCEL_TIMEOUT_MS = 10_000;
+    private static final long POLLING_TIME_SLICE = 2;
+
+    private static final String CMD_DUMP_PACKAGE_DEXOPT = "dumpsys -t 100 package dexopt";
+
+    private static final String CMD_START_POST_BOOT = "cmd jobscheduler run android 801";
+    private static final String CMD_CANCEL_POST_BOOT = "cmd jobscheduler timeout android 801";
+    private static final String CMD_START_IDLE = "cmd jobscheduler run android 800";
+    private static final String CMD_CANCEL_IDLE = "cmd jobscheduler timeout android 800";
+
+    private static final String APPLICATION_PACKAGE = "android.compilation.cts";
+    private static final String APPLICATION_APK = "CtsCompilationApp";
+    private static final String CMD_APP_ACTIVITY_LAUNCH =
+            "am start -n " + APPLICATION_PACKAGE + "/.CompilationTargetActivity";
+
+    private static final String CMD_DELETE_ODEX = "pm delete-dexopt " + APPLICATION_PACKAGE;
+
+    private static final boolean DBG_LOG_CMD = false;
+
+    // Uses internal consts defined in BackgroundDexOptService only for testing purpose.
+    private static final int STATUS_OK = 0;
+    private static final int STATUS_CANCELLED = 1;
+
+    private ITestDevice mDevice;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mDevice = getDevice();
+        // Should reboot to put the device into known states (= post boot optimization not run yet).
+        mDevice.reboot();
+        assertThat(mDevice.waitForBootComplete(REBOOT_TIMEOUT_MS)).isTrue();
+        // This requires PackageManager to be alive. So run after reboot as the previous failure
+        // may have device in booting state.
+        assumeTrue(checkDexOptEnabled());
+    }
+
+    public void testPostBootOptimizationCompleted() throws Exception {
+        // Note that post boot job runs only once until it is completed.
+        completePostBootOptimization();
+    }
+
+    public void testPostBootOptimizationCancelled() throws Exception {
+        reinstallAppPackage();
+        LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+        postJobSchedulerJob(CMD_START_POST_BOOT);
+
+        // Wait until it is started.
+        pollingCheck("Post boot start timeout", DEXOPT_TIMEOUT_MS,
+                () -> getLastExecutionTime().startTime >= timeBefore.deviceCurrentTime);
+
+        // Now cancel it.
+        executeShellCommand(CMD_CANCEL_POST_BOOT);
+
+        // Wait until it is completed or cancelled. We cannot prevent faster devices with small
+        // number of APKs to complete very quickly, so completion while cancelling can happen.
+        pollingCheck("Post boot cancel timeout", DEXOPT_CANCEL_TIMEOUT_MS,
+                () -> getLastExecutionTime().duration >= 0);
+
+        int status = getLastDexOptStatus();
+        assertThat(status).isAnyOf(STATUS_OK, STATUS_CANCELLED);
+        if (status == STATUS_CANCELLED) {
+            assertThat(checkFinishedPostBootUpdate()).isFalse();
+            // If cancelled, we can complete it by running it again.
+            completePostBootOptimization();
+        } else {
+            assertThat(checkFinishedPostBootUpdate()).isTrue();
+        }
+    }
+
+    public void testIdleOptimizationCompleted() throws Exception {
+        completePostBootOptimization();
+
+        completeIdleOptimization();
+        // idle job can run again.
+        completeIdleOptimization();
+    }
+
+    public void testIdleOptimizationCancelled() throws Exception {
+        completePostBootOptimization();
+
+        reinstallAppPackage();
+        LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+        postJobSchedulerJob(CMD_START_IDLE);
+
+        // Wait until it is started.
+        pollingCheck("Idle start timeout", DEXOPT_TIMEOUT_MS,
+                () -> getLastExecutionTime().startTime >= timeBefore.deviceCurrentTime);
+
+        // Now cancel it.
+        executeShellCommand(CMD_CANCEL_IDLE);
+
+        // Wait until it is completed or cancelled.
+        pollingCheck("Idle cancel timeout", DEXOPT_CANCEL_TIMEOUT_MS,
+                () -> getLastExecutionTime().duration >= 0);
+
+        int status = getLastDexOptStatus();
+        assertThat(status).isAnyOf(STATUS_OK, STATUS_CANCELLED);
+        if (status == STATUS_CANCELLED) {
+            // If cancelled, we can complete it by running it again.
+            completeIdleOptimization();
+        }
+    }
+
+    private String executeShellCommand(String cmd) throws Exception {
+        String result =  mDevice.executeShellCommand(cmd);
+        if (DBG_LOG_CMD) {
+            CLog.i("Executed cmd:" + cmd + ", result:" + result);
+        }
+        return result;
+    }
+
+    private void completePostBootOptimization() throws Exception {
+        reinstallAppPackage();
+        LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+        postJobSchedulerJob(CMD_START_POST_BOOT);
+
+        pollingCheck("Post boot optimization timeout", DEXOPT_TIMEOUT_MS,
+                () -> checkFinishedPostBootUpdate());
+
+        LastDeviceExecutionTime timeAfter = getLastExecutionTime();
+        assertThat(timeAfter.startTime).isAtLeast(timeBefore.deviceCurrentTime);
+        assertThat(timeAfter.duration).isAtLeast(0);
+        int status = getLastDexOptStatus();
+        assertThat(status).isEqualTo(STATUS_OK);
+    }
+
+    private void completeIdleOptimization() throws Exception {
+        reinstallAppPackage();
+        LastDeviceExecutionTime timeBefore = getLastExecutionTime();
+        postJobSchedulerJob(CMD_START_IDLE);
+
+        pollingCheck("Idle optimization timeout", DEXOPT_TIMEOUT_MS,
+                () -> {
+                    LastDeviceExecutionTime executionTime = getLastExecutionTime();
+                    return executionTime.startTime >= timeBefore.deviceCurrentTime
+                            && executionTime.duration >= 0;
+                });
+
+        int status = getLastDexOptStatus();
+        assertThat(status).isEqualTo(STATUS_OK);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Cancel all active dexopt jobs.
+        executeShellCommand(CMD_CANCEL_IDLE);
+        executeShellCommand(CMD_CANCEL_POST_BOOT);
+        mDevice.uninstallPackage(APPLICATION_PACKAGE);
+        super.tearDown();
+    }
+
+    private void postJobSchedulerJob(String cmd) throws Exception {
+        // Do retry as job may not be registered yet during boot up.
+        pollingCheck("Starting job timeout:" + cmd, DEXOPT_TIMEOUT_MS,
+                () -> {
+                    String r = executeShellCommand(cmd);
+                    return r.contains("Running");
+                });
+    }
+
+    private void reinstallAppPackage() throws Exception {
+        mDevice.uninstallPackage(APPLICATION_PACKAGE);
+
+        File apkFile = File.createTempFile(APPLICATION_APK, ".apk");
+        try (OutputStream outputStream = new FileOutputStream(apkFile)) {
+            InputStream inputStream = getClass().getResourceAsStream(
+                    "/" + APPLICATION_APK + ".apk");
+            ByteStreams.copy(inputStream, outputStream);
+        }
+        String error = mDevice.installPackage(apkFile, /* reinstall= */ false);
+
+        assertThat(error).isNull();
+
+        // Delete odex files.
+        executeShellCommand(CMD_DELETE_ODEX);
+        executeShellCommand(CMD_APP_ACTIVITY_LAUNCH);
+        // Give short time to run some code.
+        Thread.sleep(500);
+    }
+
+    private boolean checkDexOptEnabled() throws Exception {
+        return checkBooleanDumpValue("enabled");
+    }
+
+    private boolean checkFinishedPostBootUpdate() throws Exception {
+        return checkBooleanDumpValue("mFinishedPostBootUpdate");
+    }
+
+    private boolean checkBooleanDumpValue(String key) throws Exception {
+        String value = findDumpValueForKey(key);
+        assertThat(value).isNotNull();
+        return value.equals("true");
+    }
+
+    private String findDumpValueForKey(String key) throws Exception {
+        for (String line: getDexOptDumpForBgDexOpt()) {
+            String[] vals = line.split(":");
+            if (vals[0].equals(key)) {
+                return vals[1];
+            }
+        }
+        return null;
+    }
+
+    private List<String> getDexOptDumpForBgDexOpt() throws Exception {
+        String dump = executeShellCommand(CMD_DUMP_PACKAGE_DEXOPT);
+        String[] lines = dump.split("\n");
+        LinkedList<String> bgDexOptDumps = new LinkedList<>();
+        // BgDexopt state is located in the last part from the dexopt dump. So there is no separate
+        // end of the dump check.
+        boolean inBgDexOptDump = false;
+        for (int i = 0; i < lines.length; i++) {
+            if (lines[i].contains("BgDexopt state:")) {
+                inBgDexOptDump = true;
+            } else if (inBgDexOptDump) {
+                bgDexOptDumps.add(lines[i].trim());
+            }
+        }
+        // dumpsys package can expire due to the lock while bgdexopt is running.
+        if (dump.contains("DUMP TIMEOUT")) {
+            CLog.w("package dump timed out");
+            throw new TimeoutException();
+        }
+        return bgDexOptDumps;
+    }
+
+    private int getLastDexOptStatus() throws Exception {
+        String value = findDumpValueForKey("mLastExecutionStatus");
+        assertThat(value).isNotNull();
+        return Integer.parseInt(value);
+    }
+
+    private LastDeviceExecutionTime getLastExecutionTime() throws Exception {
+        long startTime = 0;
+        long duration = 0;
+        long deviceCurrentTime = 0;
+        for (String line: getDexOptDumpForBgDexOpt()) {
+            String[] vals = line.split(":");
+            switch (vals[0]) {
+                case "mLastExecutionStartTimeMs":
+                    startTime = Long.parseLong(vals[1]);
+                    break;
+                case "mLastExecutionDurationMs":
+                    duration = Long.parseLong(vals[1]);
+                    break;
+                case "now":
+                    deviceCurrentTime = Long.parseLong(vals[1]);
+                    break;
+            }
+        }
+        assertThat(deviceCurrentTime).isNotEqualTo(0);
+        return new LastDeviceExecutionTime(startTime, duration, deviceCurrentTime);
+    }
+
+    private static void pollingCheck(CharSequence message, long timeout,
+            Callable<Boolean> condition) throws Exception {
+        long expirationTime = System.currentTimeMillis() + timeout;
+        while (System.currentTimeMillis() < expirationTime) {
+            try {
+                if (condition.call()) {
+                    return;
+                }
+            } catch (TimeoutException e) {
+                // DUMP TIMEOUT has happened. Ignore it as we have to retry.
+            }
+            Thread.sleep(POLLING_TIME_SLICE);
+        }
+
+        Assert.fail(message.toString());
+    }
+
+    private static class LastDeviceExecutionTime {
+        public final long startTime;
+        public final long duration;
+        public final long deviceCurrentTime;
+
+        private LastDeviceExecutionTime(long startTime, long duration, long deviceCurrentTime) {
+            this.startTime = startTime;
+            this.duration = duration;
+            this.deviceCurrentTime = deviceCurrentTime;
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
index 89c72ed..5b19966 100644
--- a/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CertInstaller/AndroidManifest.xml
@@ -31,6 +31,8 @@
                 <action android:name="com.android.cts.certinstaller.remove_cert"/>
                 <action android:name="com.android.cts.certinstaller.verify_cert"/>
                 <action android:name="com.android.cts.certinstaller.install_keypair"/>
+                <action android:name="com.android.cts.certinstaller.done"/>
+                <action android:name="com.android.cts.certinstaller.read_esid"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </receiver>
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
index 7332521..6925810 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
@@ -18,6 +18,7 @@
 import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.Manifest.permission.START_CROSS_PROFILE_ACTIVITIES;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -130,6 +131,19 @@
                 String.valueOf(mUserSerialNumber), textView.getText());
     }
 
+    @Test(expected = SecurityException.class)
+    public void testCannotStartActivityByIntentWithStartCrossProfileActivitiesPermission() {
+        Intent intent = new Intent();
+        intent.setComponent(MainActivity.getComponentName(mContext));
+        ShellIdentityUtils.dropShellPermissionIdentity();
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
+                crossProfileApps -> crossProfileApps.startActivity(
+                        intent, mTargetUser, /* callingActivity= */ null),
+                START_CROSS_PROFILE_ACTIVITIES);
+    }
+
     @Test
     public void testCanStartActivityByIntentWithInteractAcrossUsersPermission() {
         Intent intent = new Intent();
@@ -329,6 +343,25 @@
     }
 
     @Test
+    public void testCanStartMainActivityByComponentWithStartCrossProfileActivitiesPermission() {
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+                    crossProfileApps -> mCrossProfileApps.startActivity(
+                            MainActivity.getComponentName(mContext), mTargetUser),
+                    START_CROSS_PROFILE_ACTIVITIES);
+
+            // Look for the text view to verify that MainActivity is started.
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW)),
+                    TIMEOUT_WAIT_UI);
+            assertNotNull("Failed to start main activity in target user", textView);
+            assertEquals("Main Activity is started in wrong user",
+                    String.valueOf(mUserSerialNumber), textView.getText());
+        } catch (Exception e) {
+            fail("unable to start main activity via CrossProfileApps#startActivity: " + e);
+        }
+    }
+
+    @Test
     public void testCanStartNonMainActivityByComponent() {
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
@@ -347,6 +380,25 @@
     }
 
     @Test
+    public void testCanStartNonMainActivityByComponentWithStartCrossProfileActivitiesPermission() {
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+                    crossProfileApps -> mCrossProfileApps.startActivity(
+                            NonMainActivity.getComponentName(mContext), mTargetUser),
+                    START_CROSS_PROFILE_ACTIVITIES);
+
+            // Look for the text view to verify that NonMainActivity is started.
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW_NONMAIN)),
+                    TIMEOUT_WAIT_UI);
+            assertNotNull("Failed to start non-main activity in target user", textView);
+            assertEquals("Non-Main Activity is started in wrong user",
+                    String.valueOf(mUserSerialNumber), textView.getText());
+        } catch (Exception e) {
+            fail("unable to start non-main activity via CrossProfileApps#startActivity: " + e);
+        }
+    }
+
+    @Test
     public void testCanStartNotExportedActivityByIntent() throws Exception {
         Intent nonExportedActivityIntent = new Intent();
         nonExportedActivityIntent.setComponent(NonExportedActivity.getComponentName(mContext));
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
index 61a60e4..ad51a93 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/ModifyQuietModeEnabledApp/OWNERS
@@ -1,3 +1,3 @@
 # Bug component: 168445
 file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
-pbdr@google.com
\ No newline at end of file
+pbdr@google.com
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
index 572b40b..c081f95 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DelegateApp/AndroidManifest.xml
@@ -34,7 +34,13 @@
         </receiver>
         <!--  TODO(b/176993670): remove if DpmWrapper goes away -->
         <receiver android:name="com.android.bedstead.dpmwrapper.IpcBroadcastReceiver"
-             android:exported="true"/>
+             android:exported="true">
+        <!--  TODO(b/213348113, b/213331396) - might need to explicitly set the filter below:
+            <intent-filter>
+               <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
+            </intent-filter>
+         -->
+        </receiver>
         <!--  TODO(b/176993670): remove if DpmWrapper goes away -->
         <receiver android:name="com.android.bedstead.dpmwrapper.TestAppCallbacksReceiver"
              android:exported="true"/>
diff --git a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
index b93b884..8e0f517 100644
--- a/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
+++ b/hostsidetests/devicepolicy/app/DelegateApp/src/com/android/cts/delegate/GeneralDelegateTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.delegate;
 
 import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
 import android.os.Bundle;
 import android.test.MoreAsserts;
 import android.util.Log;
@@ -57,4 +58,18 @@
                     expected.getMessage());
         }
     }
+  
+    public void testSettingAdminComponentNameThrowsException() {
+        final String myPackageName = getInstrumentation().getContext().getPackageName();
+        final ComponentName myComponentName = new ComponentName(myPackageName,
+                GeneralDelegateTest.class.getName());
+
+        try {
+            mDpm.setUninstallBlocked(myComponentName, myPackageName, true);
+            fail("Expected SecurityException not thrown");
+        } catch (SecurityException expected) {
+            MoreAsserts.assertContainsRegex("does not exist or is not owned by uid",
+                    expected.getMessage());
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
deleted file mode 100644
index 0f4f22d..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
+++ /dev/null
@@ -1,51 +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.cts.deviceadmin;
-
-import android.os.Build;
-
-/**
- * Tests that:
- * - need to be run as device admin (as opposed to device owner) and
- * - require resetting the password at the end.
- *
- * Note: when adding a new method, make sure to add a corresponding method in
- * BaseDeviceAdminHostSideTest.
- */
-public class DeviceAdminPasswordTest extends BaseDeviceAdminTest {
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        assertNotDeviceOwner();
-    }
-
-    public void testResetPasswordDeprecated() {
-        if (getTargetSdkLevel() < Build.VERSION_CODES.N) {
-            assertFalse(dpm.resetPassword("1234", 0));
-        } else {
-            try {
-                dpm.resetPassword("1234", 0);
-                fail("resetPassword() should throw SecurityException");
-            } catch (SecurityException e) { }
-        }
-    }
-
-    private int getTargetSdkLevel() {
-        return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index 22c17a2..baaac8d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -65,10 +65,6 @@
         mDevicePolicyManager.setRequiredPasswordComplexity(PASSWORD_COMPLEXITY_HIGH);
     }
 
-    public void testLockNowLogged() {
-        mDevicePolicyManager.lockNow(0);
-    }
-
     public void testSetKeyguardDisabledFeaturesLogged() {
         mDevicePolicyManager.setKeyguardDisabledFeatures(
                 ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_FEATURES_NONE);
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
index 23de557..b0ef424 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyAppStreamingPolicyTest.java
@@ -22,9 +22,9 @@
 
 public class NearbyAppStreamingPolicyTest extends BaseDeviceAdminTest {
 
-    public void testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault() {
+    public void testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly() {
         assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
-                .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+                .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
     }
 
     public void testSetNearbyAppStreamingPolicy_changesPolicy() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
index ae0a26f..cd5abf3 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/NearbyNotificationStreamingPolicyTest.java
@@ -22,9 +22,9 @@
 
 public class NearbyNotificationStreamingPolicyTest extends BaseDeviceAdminTest {
 
-    public void testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault() {
+    public void testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly() {
         assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
-                .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+                .isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
     }
 
     public void testSetNearbyNotificationStreamingPolicy_changesPolicy() {
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
deleted file mode 100644
index c660309..0000000
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ResetPasswordTest.java
+++ /dev/null
@@ -1,48 +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.cts.deviceandprofileowner;
-
-import android.os.Build;
-
-
-/**
- * Test cases for {@link android.app.admin.DevicePolicyManager#resetPassword(String, int)}.
- *
- * As of R, resetPassword is fully deprecated: DPCs targeting Sdk level O or above will continue
- * to receive a SecurityException when calling this, while DPC targeting N or below will just get
- * a silent failure of API returning {@code false}. This class tests these two negative cases.
- *
- */
-public class ResetPasswordTest extends BaseDeviceAdminTest {
-
-    public void testResetPasswordDeprecated() {
-        waitUntilUserUnlocked();
-
-        if (getTargetSdkLevel() >= Build.VERSION_CODES.O) {
-            try {
-                mDevicePolicyManager.resetPassword("1234", 0);
-                fail("resetPassword() should throw SecurityException");
-            } catch (SecurityException e) { }
-
-        } else {
-            assertFalse(mDevicePolicyManager.resetPassword("1234", 0));
-        }
-    }
-
-    private int getTargetSdkLevel() {
-        return mContext.getApplicationContext().getApplicationInfo().targetSdkVersion;
-    }
-}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
index 54bd5d4..621a15d 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/BaseUserRestrictionsTest.java
@@ -73,6 +73,11 @@
             UserManager.DISALLOW_BLUETOOTH_SHARING,
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_MICROPHONE_TOGGLE,
+            UserManager.DISALLOW_CHANGE_WIFI_STATE,
+            UserManager.DISALLOW_WIFI_TETHERING,
+            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     };
 
     /**
@@ -96,6 +101,10 @@
             // UserManager.DISALLOW_DATA_ROAMING, // Not set during CTS
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_MICROPHONE_TOGGLE,
+            UserManager.DISALLOW_CHANGE_WIFI_STATE,
+            UserManager.DISALLOW_WIFI_TETHERING,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG,
 
             // PO can set them too, but when DO sets them, they're global.
             UserManager.DISALLOW_ADJUST_VOLUME,
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
index 93b6817..1c672df 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/userrestrictions/DeviceOwnerUserRestrictionsTest.java
@@ -63,6 +63,11 @@
             UserManager.DISALLOW_UNIFIED_PASSWORD,
             UserManager.DISALLOW_CAMERA_TOGGLE,
             UserManager.DISALLOW_MICROPHONE_TOGGLE,
+            UserManager.DISALLOW_CHANGE_WIFI_STATE,
+            UserManager.DISALLOW_WIFI_TETHERING,
+            UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
+            UserManager.DISALLOW_WIFI_DIRECT,
+            UserManager.DISALLOW_ADD_WIFI_CONFIG
     };
 
     public static final String[] DISALLOWED = new String[] {
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 3863d90..47fd748 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -49,6 +49,8 @@
                  android:resource="@xml/device_admin"/>
             <intent-filter>
                 <action android:name="android.app.action.DEVICE_ADMIN_ENABLED"/>
+                <!--  TODO(b/176993670): remove WRAPPED_MANAGER_CALL if DpmWrapper goes away -->
+                <action android:name="com.android.bedstead.dpmwrapper.action.WRAPPED_MANAGER_CALL"/>
             </intent-filter>
         </receiver>
         <receiver android:name="com.android.cts.deviceowner.CreateAndManageUserTest$SecondaryUserAdminReceiver"
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
index a515d6b..584da16 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.bp
@@ -35,6 +35,7 @@
         "testng",
         "androidx.legacy_legacy-support-v4",
         "devicepolicy-deviceside-common",
+        "permission-test-util-lib",
     ],
     min_sdk_version: "27",
     // tag this module as a cts test artifact
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 3b8b877..0a049c9 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -42,6 +42,7 @@
     <uses-permission android:name="android.permission.WRITE_CALENDAR"/>
     <uses-permission android:name="android.permission.REQUEST_PASSWORD_COMPLEXITY"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
     <application android:largeHeap="true"
                  android:testOnly="true">
@@ -205,6 +206,7 @@
             <intent-filter>
                 <action android:name="com.android.cts.managedprofile.WIPE_DATA"/>
                 <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITH_REASON"/>
+                <action android:name="com.android.cts.managedprofile.WIPE_DATA_WITHOUT_REASON"/>
             </intent-filter>
         </receiver>
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
index c24d01c..533b322 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/DevicePolicyManagerParentSupportTest.java
@@ -177,11 +177,6 @@
         assertThat(actualMaximumTimeToLock).isEqualTo(maximumTimeToLock);
     }
 
-    public void testLockNow_onParent_isSupported() {
-        mParentDevicePolicyManager.lockNow();
-        // Will fail if a SecurityException is thrown.
-    }
-
     public void testSetAndGetKeyguardDisabledFeatures_onParent() {
         mParentDevicePolicyManager.setKeyguardDisabledFeatures(
                 ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_TRUST_AGENTS);
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
index b53bd03..1f519b3 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/NotificationListenerTest.java
@@ -15,6 +15,8 @@
  */
 package com.android.cts.managedprofile;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.admin.DevicePolicyManager;
@@ -24,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Bundle;
+import android.permission.cts.PermissionUtils;
 import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 
@@ -53,8 +56,8 @@
 
     private static final String PARAM_PROFILE_ID = "profile-id";
 
-    static final String SENDER_COMPONENT =
-            "com.android.cts.managedprofiletests.notificationsender/.SendNotification";
+    static final String SENDER_PACKAGE = "com.android.cts.managedprofiletests.notificationsender";
+    static final String SENDER_COMPONENT = SENDER_PACKAGE + "/.SendNotification";
 
     private final LocalBroadcastReceiver mReceiver = new LocalBroadcastReceiver();
     private Context mContext;
@@ -68,6 +71,7 @@
         mDpm = mContext.getSystemService(DevicePolicyManager.class);
         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         mProfileUserId = getParam(InstrumentationRegistry.getArguments(), PARAM_PROFILE_ID);
+        PermissionUtils.grantPermission(SENDER_PACKAGE, POST_NOTIFICATIONS);
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_NOTIFICATION_POSTED);
         filter.addAction(ACTION_NOTIFICATION_REMOVED);
@@ -77,6 +81,7 @@
 
     @After
     public void tearDown() throws Exception {
+        PermissionUtils.revokePermission(SENDER_PACKAGE, POST_NOTIFICATIONS);
         LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReceiver);
         toggleNotificationListener(false);
     }
diff --git a/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml b/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
index 0f5ac6d..4417c5e 100644
--- a/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/NotificationSender/AndroidManifest.xml
@@ -18,6 +18,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.cts.managedprofiletests.notificationsender">
 
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
+
     <application>
       <activity android:name=".SendNotification"
                 android:exported="true">
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
deleted file mode 100644
index 3ac997b..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/Android.bp
+++ /dev/null
@@ -1,43 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CtsSeparateProfileChallengeApp",
-    defaults: ["cts_defaults"],
-    platform_apis: true,
-    min_sdk_version: "27",
-    srcs: ["src/**/*.java"],
-    libs: [
-        "android.test.runner.stubs",
-        "junit",
-        "android.test.base.stubs",
-    ],
-    static_libs: [
-        "ctstestrunner-axt",
-        "compatibility-device-util-axt",
-        "ub-uiautomator",
-    ],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "vts10",
-        "general-tests",
-	"sts",
-        "mts",
-    ],
-}
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml
deleted file mode 100644
index 600b5c1..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/AndroidManifest.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.android.cts.separateprofilechallenge" >
-
-    <uses-sdk android:minSdkVersion="27"/>
-
-    <uses-permission android:name="WRITE_SECURE_SETTINGS"/>
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.separateprofilechallenge"
-                     android:label="Separate Profile Challenge Permission  CTS tests"/>
-</manifest>
diff --git a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java b/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java
deleted file mode 100644
index e1ce7d6..0000000
--- a/hostsidetests/devicepolicy/app/SeparateProfileChallenge/src/com/android/cts/separateprofilechallenge/SeparateProfileChallengePermissionsTest.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.separateprofilechallenge;
-
-import android.app.admin.DevicePolicyManager;
-import android.content.Context;
-import android.platform.test.annotations.AsbSecurityTest;
-import android.test.AndroidTestCase;
-
-import androidx.test.runner.AndroidJUnitRunner;
-
-import static org.junit.Assert.assertNotNull;
-
-public class SeparateProfileChallengePermissionsTest extends AndroidTestCase {
-
-    public void testSeparateProfileChallengePermissions() throws Exception {
-        DevicePolicyManager dpm = (DevicePolicyManager)
-                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        assertNotNull(dpm);
-        try {
-            dpm.isSeparateProfileChallengeAllowed(0); /* Try to use USER_SYSTEM */
-            fail("The user must be system to call isSeparateProfileChallengeAllowed().");
-        } catch (SecurityException ignore) {
-            // That's what we want!
-        } catch (NoSuchMethodError err) {
-            // API unavailable - pass
-        }
-    }
-}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
index a6696aa..afa1a3e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDeviceAdminHostSideTest.java
@@ -87,12 +87,4 @@
     public void testRunDeviceAdminTest() throws Exception {
         runTests(getDeviceAdminApkPackage(), "DeviceAdminTest");
     }
-
-    @Test
-    public void testResetPasswordDeprecated() throws Exception {
-        assumeHasSecureLockScreenFeature();
-
-        runTests(getDeviceAdminApkPackage(), "DeviceAdminPasswordTest",
-                        "testResetPasswordDeprecated");
-    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 96b8ee3..34b0642 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -88,6 +88,7 @@
     private static final String FEATURE_TELEPHONY = "android.hardware.telephony";
     private static final String FEATURE_SECURE_LOCK_SCREEN = "android.software.secure_lock_screen";
     private static final String FEATURE_WIFI = "android.hardware.wifi";
+    private static final String FEATURE_WATCH = "android.hardware.type.watch";
 
     //The maximum time to wait for user to be unlocked.
     private static final long USER_UNLOCK_TIMEOUT_SEC = 30;
@@ -177,6 +178,9 @@
     protected int mDeviceOwnerUserId;
     protected int mPrimaryUserId;
 
+    /** Is test running on a watch */
+    protected boolean mIsWatch;
+
     /** Record the initial user ID. */
     protected int mInitialUserId;
 
@@ -206,6 +210,7 @@
         mSupportsMultiUser = getMaxNumberOfUsersSupported() > 1;
         mFixedPackages = getDevice().getInstalledPackageNames();
         mBuildHelper = new CompatibilityBuildHelper(getBuild());
+        mIsWatch = hasDeviceFeature(FEATURE_WATCH);
 
         String propertyValue = getDevice().getProperty("ro.product.first_api_level");
         if (propertyValue != null && !propertyValue.isEmpty()) {
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index 2220d80..44ad152 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -112,10 +112,18 @@
         if (!mHasManagedUserFeature) {
             return;
         }
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartMainActivityByComponent");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartNonMainActivityByComponent");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartNotExportedActivityByComponent");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartActivityInOtherPackageByComponent");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartMainActivityByComponent");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartMainActivityByComponentWithStartCrossProfileActivitiesPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartNonMainActivityByComponent");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartNonMainActivityByComponentWithStartCrossProfileActivitiesPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCannotStartNotExportedActivityByComponent");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCannotStartActivityInOtherPackageByComponent");
     }
 
     @LargeTest
@@ -138,10 +146,16 @@
         if (!mHasManagedUserFeature) {
             return;
         }
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCannotStartActivityByIntentWithNoPermissions");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossProfilesPermission");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossUsersPermission");
-        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS, "testCanStartActivityByIntentWithInteractAcrossUsersFullPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCannotStartActivityByIntentWithNoPermissions");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartActivityByIntentWithInteractAcrossProfilesPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCannotStartActivityByIntentWithStartCrossProfileActivitiesPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartActivityByIntentWithInteractAcrossUsersPermission");
+        verifyCrossProfileAppsApi(mProfileId, mPrimaryUserId, START_ACTIVITY_TEST_CLASS,
+                "testCanStartActivityByIntentWithInteractAcrossUsersFullPermission");
     }
 
     @LargeTest
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index 8c8f543..7c9c5a8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -609,62 +609,6 @@
         executeDeviceTestMethod(".ApplicationHiddenTest", "testCannotHidePolicyExemptApps");
     }
 
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_userRestrictionAddAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
-            executeAccountTest("testAddAccount_blocked");
-        } finally {
-            // Ensure we clear the user restriction
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
-        }
-        executeAccountTest("testAddAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, true, mUserId);
-            executeAccountTest("testRemoveAccount_blocked");
-        } finally {
-            // Ensure we clear the user restriction
-            changeUserRestrictionOrFail(DISALLOW_MODIFY_ACCOUNTS, false, mUserId);
-        }
-        executeAccountTest("testRemoveAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_disabledAddAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-            executeAccountTest("testAddAccount_blocked");
-        } finally {
-            // Ensure we remove account management policies
-            changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-        }
-        executeAccountTest("testAddAccount_allowed");
-    }
-
-    // TODO(b/197491427): AccountManager support in TestApp
-    @Test
-    public void testAccountManagement_disabledRemoveAccount() throws Exception {
-        installAppAsUser(ACCOUNT_MANAGEMENT_APK, mUserId);
-        try {
-            changeAccountManagement(COMMAND_BLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-            executeAccountTest("testRemoveAccount_blocked");
-        } finally {
-            // Ensure we remove account management policies
-            changeAccountManagement(COMMAND_UNBLOCK_ACCOUNT_TYPE, ACCOUNT_TYPE, mUserId);
-        }
-        executeAccountTest("testRemoveAccount_allowed");
-    }
-
     @Test
     public void testDelegatedCertInstaller() throws Exception {
         installAppAsUser(CERT_INSTALLER_APK, mUserId);
@@ -1093,14 +1037,6 @@
                     .build());
     }
 
-    /** Test for resetPassword for all devices. */
-    @Test
-    public void testResetPasswordDeprecated() throws Exception {
-        assumeHasSecureLockScreenFeature();
-
-        executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
-    }
-
     @Test
     public void testPasswordSufficientInitially() throws Exception {
         executeDeviceTestClass(".PasswordSufficientInitiallyTest");
@@ -1386,16 +1322,6 @@
     }
 
     @Test
-    public void testLockNowLogged() throws Exception {
-        assertMetricsLogged(getDevice(), () -> {
-            executeDeviceTestMethod(".DevicePolicyLoggingTest", "testLockNowLogged");
-        }, new DevicePolicyEventWrapper.Builder(EventId.LOCK_NOW_VALUE)
-                .setAdminPackageName(DEVICE_ADMIN_PKG)
-                .setInt(0)
-                .build());
-    }
-
-    @Test
     public void testSetKeyguardDisabledFeaturesLogged() throws Exception {
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(
@@ -1705,11 +1631,11 @@
 
     // TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
     @Test
-    public void testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault()
+    public void testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly()
             throws Exception {
         executeDeviceTestMethod(
                 ".NearbyNotificationStreamingPolicyTest",
-                "testGetNearbyNotificationStreamingPolicy_getsNearbyStreamingDisabledAsDefault");
+                "testGetNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly");
     }
 
     // TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
@@ -1722,11 +1648,11 @@
 
     // TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
     @Test
-    public void testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault()
+    public void testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly()
             throws Exception {
         executeDeviceTestMethod(
                 ".NearbyAppStreamingPolicyTest",
-                "testGetNearbyAppStreamingPolicy_getsNearbyStreamingDisabledAsDefault");
+                "testGetNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly");
     }
 
     // TODO(b/184175078): Migrate test to Bedstead when the infra is ready.
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
index 4422d6b..51d988c 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTestApi25.java
@@ -61,13 +61,6 @@
                 "testPasswordConstraintsDoesntThrowAndPreservesValuesPreR");
     }
 
-    @Test
-    public void testResetPasswordDeprecated() throws Exception {
-        assumeHasSecureLockScreenFeature();
-
-        executeDeviceTestMethod(".ResetPasswordTest", "testResetPasswordDeprecated");
-    }
-
     protected void executeDeviceTestClass(String className) throws Exception {
         runDeviceTestsAsUser(DEVICE_ADMIN_PKG, className, mUserId);
     }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 17c2d48..116147d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
 
 import android.platform.test.annotations.AsbSecurityTest;
 import android.platform.test.annotations.FlakyTest;
@@ -103,6 +104,7 @@
 
     @Test
     public void testProxyPacProxyTest() throws Exception {
+        assumeFalse("Test does not apply to WearOS", mIsWatch);
         executeDeviceOwnerTest("proxy.PacProxyTest");
     }
 
@@ -747,6 +749,7 @@
 
     @Test
     public void testSetStatusBarDisabledLogged() throws Exception {
+        assumeFalse("Test does not apply to WearOS", mIsWatch);
         assertMetricsLogged(getDevice(), () -> {
             executeDeviceTestMethod(".DevicePolicyLoggingTest", "testSetStatusBarDisabledLogged");
         }, new DevicePolicyEventWrapper.Builder(EventId.SET_STATUS_BAR_DISABLED_VALUE)
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index 0d11e0a..b11c459 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -38,15 +38,6 @@
 
     @FlakyTest
     @Test
-    public void testLockNowWithKeyEviction() throws Exception {
-        assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
-
-        changeUserCredential(TEST_PASSWORD, null, mProfileUserId);
-        lockProfile();
-    }
-
-    @FlakyTest
-    @Test
     public void testResetPasswordWithTokenBeforeUnlock() throws Exception {
         assumeHasFileBasedEncryptionAndSecureLockScreenFeatures();
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
index 75af1c8..7012f18 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -140,22 +140,6 @@
         executeDeviceTestClass(".AdminConfiguredNetworksTest");
     }
 
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
-            reason = "Will be migrated to new test infra")
-    public void testAccountManagement_userRestrictionAddAccount() throws Exception {
-        super.testAccountManagement_userRestrictionAddAccount();
-    }
-
-    @Override
-    @Test
-    @TemporarilyIgnoreOnHeadlessSystemUserMode(bugId = "197909577",
-            reason = "Will be migrated to new test infra")
-    public void testAccountManagement_userRestrictionRemoveAccount() throws Exception {
-        super.testAccountManagement_userRestrictionRemoveAccount();
-    }
-
     @Test
     public void testSetTime() throws Exception {
         assertMetricsLogged(getDevice(), () -> {
@@ -473,13 +457,6 @@
 
     @Override
     @Test
-    @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't have credentials")
-    public void testResetPasswordDeprecated() throws Exception {
-        super.testResetPasswordDeprecated();
-    }
-
-    @Override
-    @Test
     @IgnoreOnHeadlessSystemUserMode(reason = "Headless system user doesn't launch activities")
     public void testCreateAdminSupportIntent() throws Exception {
         super.testCreateAdminSupportIntent();
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
deleted file mode 100644
index 18e8ed9..0000000
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/SeparateProfileChallengeTest.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.devicepolicy;
-
-import static com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.FEATURE_MANAGED_USERS;
-
-import android.platform.test.annotations.AsbSecurityTest;
-
-import com.android.cts.devicepolicy.DeviceAdminFeaturesCheckerRule.RequiresAdditionalFeatures;
-
-import org.junit.Test;
-
-/**
- * Host side tests for separate profile challenge permissions.
- * Run the CtsSeparateProfileChallengeApp device side test.
- */
-@RequiresAdditionalFeatures({FEATURE_MANAGED_USERS})
-public class SeparateProfileChallengeTest extends BaseDevicePolicyTest {
-    private static final String SEPARATE_PROFILE_PKG = "com.android.cts.separateprofilechallenge";
-    private static final String SEPARATE_PROFILE_APK = "CtsSeparateProfileChallengeApp.apk";
-    private static final String SEPARATE_PROFILE_TEST_CLASS =
-        ".SeparateProfileChallengePermissionsTest";
-    private String mPreviousHiddenApiPolicy = "0";
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-
-        setHiddenApiPolicyOn();
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-
-        removeTestUsers();
-        getDevice().uninstallPackage(SEPARATE_PROFILE_PKG);
-        setHiddenApiPolicyPreviousOrOff();
-        super.tearDown();
-    }
-
-    @Test
-    @AsbSecurityTest(cveBugId = 128599668)
-    public void testSeparateProfileChallengePermissions() throws Exception {
-        assumeCanCreateOneManagedUser();
-
-        // Create managed profile.
-        final int profileUserId = createManagedProfile(mPrimaryUserId);
-        // createManagedProfile doesn't start the user automatically.
-        startUser(profileUserId);
-        installAppAsUser(SEPARATE_PROFILE_APK, profileUserId);
-        executeSeparateProfileChallengeTest(profileUserId);
-    }
-
-    protected void setHiddenApiPolicyOn() throws Exception {
-        mPreviousHiddenApiPolicy = getDevice().executeShellCommand(
-                "settings get global hidden_api_policy_p_apps");
-        executeShellCommand("settings put global hidden_api_policy_p_apps 1");
-    }
-
-    protected void setHiddenApiPolicyPreviousOrOff() throws Exception {
-        executeShellCommand("settings put global hidden_api_policy_p_apps "
-            + mPreviousHiddenApiPolicy);
-    }
-
-    private void executeSeparateProfileChallengeTest(int userId) throws Exception {
-        runDeviceTestsAsUser(SEPARATE_PROFILE_PKG, SEPARATE_PROFILE_TEST_CLASS, userId);
-    }
-}
diff --git a/hostsidetests/edi/Android.bp b/hostsidetests/edi/Android.bp
index 1b32a4b..f3feb1a 100644
--- a/hostsidetests/edi/Android.bp
+++ b/hostsidetests/edi/Android.bp
@@ -24,6 +24,7 @@
         "cts",
         "gts",
         "general-tests",
+        "tvts",
     ],
     libs: [
         "compatibility-host-util",
@@ -36,4 +37,6 @@
         "modules-utils-build-testing",
         "hamcrest-library",
     ],
+    data: [":SharedLibraryInfoTestApp"],
+    per_testcase_directory: true,
 }
diff --git a/hostsidetests/graphics/gpumetrics/Android.bp b/hostsidetests/graphics/gpumetrics/Android.bp
new file mode 100644
index 0000000..b67305c
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/Android.bp
@@ -0,0 +1,35 @@
+// Copyright 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsGpuMetricsHostTestCases",
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    data: [
+        ":CtsFramestatsTestApp",
+    ],
+    per_testcase_directory: true,
+}
diff --git a/hostsidetests/graphics/gpumetrics/AndroidTest.xml b/hostsidetests/graphics/gpumetrics/AndroidTest.xml
new file mode 100644
index 0000000..2681fb9
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs CtsGpuMetricsHostTestCases">
+
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="graphics" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsFramestatsTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="jar" value="CtsGpuMetricsHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/graphics/gpumetrics/TEST_MAPPING b/hostsidetests/graphics/gpumetrics/TEST_MAPPING
new file mode 100644
index 0000000..fec19ff
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsGpuMetricsHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java b/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java
new file mode 100644
index 0000000..0507669
--- /dev/null
+++ b/hostsidetests/graphics/gpumetrics/src/com/android/cts/graphics/GpuWorkDumpsysTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.graphics;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class GpuWorkDumpsysTest extends BaseHostJUnit4Test {
+
+  private static final String DUMPSYS_COMMAND = "dumpsys gpu --gpuwork";
+  private static final String TEST_PKG = "com.android.cts.framestatstestapp";
+  private static final int PAUSE_MILLIS = 3000;
+
+  private CommandResult assertShellCommand(String command) throws DeviceNotAvailableException {
+    CommandResult commandResult = getDevice().executeShellV2Command(command);
+
+    // It must succeed.
+    assertEquals(
+        String.format("Failed shell command: %s", command),
+        CommandStatus.SUCCESS,
+        commandResult.getStatus());
+
+    return commandResult;
+  }
+
+  private long getTestAppUid() throws DeviceNotAvailableException {
+    int currentUser = getDevice().getCurrentUser();
+    CommandResult commandResult =
+        assertShellCommand(
+            String.format("cmd package list packages -U --user %d %s", currentUser, TEST_PKG));
+    String[] parts = commandResult.getStdout().split(":");
+    // Example output:
+    // package:com.android.cts.framestatstestapp uid:10183
+    assertTrue(
+        String.format("Unexpected output getting package uid:\n%s", commandResult.getStdout()),
+        parts.length > 2);
+
+    long appUid = Long.parseLong(parts[2].trim());
+    assertTrue(String.format("Unexpected app uid: %d", appUid), appUid > 10000);
+    return appUid;
+  }
+
+  @Test
+  public void testOutputFormat() throws Exception {
+    // Execute dumpsys command.
+    CommandResult commandResult = assertShellCommand(DUMPSYS_COMMAND);
+
+    // If the dumpsys command output indicates that the GPU information is not available then the
+    // test ends here.
+    assumeFalse(
+        "GPU time in state information was not available.",
+        commandResult.getStdout().contains("GPU time in state information is not available"));
+
+    // Turn screen on.
+    assertShellCommand("input keyevent KEYCODE_WAKEUP");
+    Thread.sleep(PAUSE_MILLIS);
+
+    // Skip lock screen.
+    assertShellCommand("wm dismiss-keyguard");
+    Thread.sleep(PAUSE_MILLIS);
+
+    // Start basic app.
+    assertShellCommand("am start -W -S " + TEST_PKG);
+    Thread.sleep(PAUSE_MILLIS);
+
+    // Get the UID of the test app.
+    long appUid = getTestAppUid();
+
+    // Execute dumpsys command again.
+    commandResult = assertShellCommand(DUMPSYS_COMMAND);
+
+    LogUtil.CLog.i("dumpsys output:\n%s", commandResult.getStdout());
+
+    String[] lines = commandResult.getStdout().trim().split("\n");
+    int i = 0;
+    for (; i < lines.length; ++i) {
+      if (lines[i].startsWith("uid/freq: 0MHz")) {
+        break;
+      }
+    }
+    assertTrue("Could not find uid/freq header in output", i < lines.length);
+
+    Pattern uidInfoPattern = Pattern.compile(String.format("(%d): \\d+", appUid));
+    for (; i < lines.length; ++i) {
+      Matcher matcher = uidInfoPattern.matcher(lines[i]);
+      if (!matcher.lookingAt()) {
+        continue;
+      }
+      if (appUid == Long.parseLong(matcher.group(1))) {
+        break;
+      }
+    }
+    assertTrue(String.format("Could not find UID %d in output", appUid), i < lines.length);
+  }
+}
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
index 2cce86d..cf1c432 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/BaseHdmiCecCtsTest.java
@@ -162,10 +162,10 @@
         String line;
         String pattern =
                 "(.*?)"
-                        + "(mAddress: )"
+                        + "(mDeviceInfo:)(.*)(logical_address: )"
                         + "(?<"
                         + "logicalAddress"
-                        + ">\\p{Digit}{1,2})"
+                        + ">0x\\p{XDigit}{2})"
                         + "(.*?)";
         Pattern p = Pattern.compile(pattern);
         try {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
index 72a2cf0..8c12d54 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecAudioReturnChannelControlTest.java
@@ -20,18 +20,18 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.error.CecClientWrapperException;
-import android.hdmicec.cts.error.ErrorCodes;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Ignore;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 /** HDMI CEC test to test audio return channel control (Section 11.2.17) */
 @Ignore("b/162820841")
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
index 1b36a61..b36c673 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecInvalidMessagesTest.java
@@ -21,9 +21,9 @@
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
+import android.hdmicec.cts.LogicalAddress;
 import android.hdmicec.cts.error.CecClientWrapperException;
 import android.hdmicec.cts.error.ErrorCodes;
-import android.hdmicec.cts.LogicalAddress;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
index 6c251a2..0ffc025 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecLogicalAddressTest.java
@@ -21,20 +21,17 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.HdmiCecClientWrapper;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.LogicalAddress;
-import android.hdmicec.cts.RequiredPropertyRule;
-import android.hdmicec.cts.RequiredFeatureRule;
 
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Ignore;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 /** HDMI CEC test to verify logical address after device reboot (Section 10.2.5) */
 @Ignore("b/162820841")
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
index e27ae7e..3acab1c 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/audio/HdmiCecSystemAudioModeTest.java
@@ -19,8 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import com.google.common.collect.Range;
-
 import android.hdmicec.cts.AudioManagerHelper;
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
@@ -30,12 +28,14 @@
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
+import com.google.common.collect.Range;
+
 import org.junit.After;
 import org.junit.Ignore;
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 import java.util.concurrent.TimeUnit;
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
index 6541ca6..e8b1d8d 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/common/HdmiCecSystemStandbyTest.java
@@ -36,7 +36,9 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-/** HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3) */
+/**
+ * HDMI CEC test to verify the device handles standby correctly (Section 11.1.3, 11.2.3)
+ */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecSystemStandbyTest extends BaseHdmiCecCtsTest {
 
@@ -50,7 +52,8 @@
 
     @Rule
     public RuleChain ruleChain =
-            RuleChain.outerRule(CecRules.requiresCec(this))
+            RuleChain
+                    .outerRule(CecRules.requiresCec(this))
                     .around(CecRules.requiresLeanback(this))
                     .around(hdmiCecClient);
 
@@ -83,7 +86,7 @@
                 WakeLockHelper.acquirePartialWakeLock(device);
                 hdmiCecClient.sendCecMessage(source, LogicalAddress.BROADCAST, CecOperand.STANDBY);
                 checkStandbyAndWakeUp();
-    }
+            }
         }
     }
 
@@ -100,7 +103,7 @@
                 WakeLockHelper.acquirePartialWakeLock(device);
                 hdmiCecClient.sendCecMessage(source, CecOperand.STANDBY);
                 checkStandbyAndWakeUp();
-    }
+            }
         }
     }
 
@@ -132,7 +135,7 @@
         mLogicalAddresses.add(LogicalAddress.AUDIO_SYSTEM);
 
         if (hasDeviceType(HdmiCecConstants.CEC_DEVICE_TYPE_TV)) {
-            // Add logical addresses 13, 14 only for TV panel tests.
+            //Add logical addresses 13, 14 only for TV panel tests.
             mLogicalAddresses.add(LogicalAddress.RESERVED_2);
             mLogicalAddresses.add(LogicalAddress.SPECIFIC_USE);
         }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
index 76c0688..949a3d5 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecActiveTrackingTest.java
@@ -34,14 +34,14 @@
 import java.util.concurrent.TimeUnit;
 
 /**
- * HDMI CEC tests verifying power status related messages of the device (CEC 2.0 CTS Section 7.6)
+ * HDMI CEC tests verifying the active tracking mechanism of the CEC network for Playback devices
  */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public final class HdmiCecActiveTrackingTest extends BaseHdmiCecCtsTest {
     // Delay to allow the DUT to poll all the non-local logical addresses (seconds)
     private static final int POLLING_WAIT_TIME = 5;
-    // Delay to wait for the HotplugDetectionAction to pass (seconds)
-    private static final int HOTPLUG_WAIT_TIME = 60;
+    // Delay to wait for the HotplugDetectionAction to start (milliseconds)
+    private static final int HOTPLUG_WAIT_TIME = 60000;
 
     public HdmiCecActiveTrackingTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_PLAYBACK_DEVICE);
@@ -65,13 +65,13 @@
 
     /**
      * Tests that the DUT removes a device from the network, when it doesn't answer to the polling
-     * message sent by HotplugDetection action.
+     * message sent by HotplugDetectionAction.
      */
     @Test
     public void cect_RemoveDeviceFromNetwork() throws Exception {
         // Wait for the device discovery action to pass.
         TimeUnit.SECONDS.sleep(POLLING_WAIT_TIME);
-        // Add Playback 2 in the network.
+        // Add an external playback device to the network.
         int playback2PhysicalAddress = createUnusedPhysicalAddress(getDumpsysPhysicalAddress());
         String formattedPhysicalAddress = CecMessage.formatParams(playback2PhysicalAddress,
                 HdmiCecConstants.PHYSICAL_ADDRESS_LENGTH);
@@ -90,8 +90,11 @@
                 CecOperand.SET_OSD_NAME,
                 CecMessage.convertStringToHexParams(deviceName)
         );
-        // Wait for the first HotplugDetection action to pass.
-        TimeUnit.SECONDS.sleep(HOTPLUG_WAIT_TIME);
+        // Wait for the first HotplugDetectionAction to start and leave enough time to poll all
+        // devices once.
+        hdmiCecClient.checkExpectedOutput(LogicalAddress.SPECIFIC_USE, CecOperand.POLL,
+                HOTPLUG_WAIT_TIME + HdmiCecConstants.DEVICE_WAIT_TIME_MS);
+        TimeUnit.SECONDS.sleep(HdmiCecConstants.DEVICE_WAIT_TIME_SECONDS);
         String deviceList = getDeviceList();
         assertThat(deviceList).doesNotContain(deviceName);
     }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
index afe78d4..459f260 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecDeviceOsdNameTest.java
@@ -29,9 +29,9 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 /** HDMI CEC tests related to the device reporting the device OSD name (Section 11.2.11) */
 @RunWith(DeviceJUnit4ClassRunner.class)
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
index e715f91..c7e0a9e 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecRoutingControlTest.java
@@ -28,9 +28,9 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
 
 import java.util.concurrent.TimeUnit;
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
index 7f666e4..4826de5 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecTvPowerToggleTest.java
@@ -29,10 +29,9 @@
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 import org.junit.Rule;
+import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
-import org.junit.Test;
-
 
 import java.util.concurrent.TimeUnit;
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
index 1775f5e..d1b58ef 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecRoutingControlTest.java
@@ -21,11 +21,11 @@
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecMessage;
 import android.hdmicec.cts.CecOperand;
-import android.hdmicec.cts.error.CecClientWrapperException;
-import android.hdmicec.cts.error.ErrorCodes;
 import android.hdmicec.cts.HdmiCecConstants;
 import android.hdmicec.cts.HdmiControlManagerUtility;
 import android.hdmicec.cts.LogicalAddress;
+import android.hdmicec.cts.error.CecClientWrapperException;
+import android.hdmicec.cts.error.ErrorCodes;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
index 4efab29..0f7b3a9 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/tv/HdmiCecTvStandbyTest.java
@@ -16,6 +16,8 @@
 
 package android.hdmicec.cts.tv;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.hdmicec.cts.BaseHdmiCecCtsTest;
 import android.hdmicec.cts.CecOperand;
 import android.hdmicec.cts.HdmiCecConstants;
@@ -30,12 +32,13 @@
 import org.junit.runner.RunWith;
 
 
-import static com.google.common.truth.Truth.assertWithMessage;
-
 /** HDMI CEC tests for system standby features (Section 11.1.3) */
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class HdmiCecTvStandbyTest extends BaseHdmiCecCtsTest {
 
+    private static final String TV_SEND_STANDBY_ON_SLEEP = "tv_send_standby_on_sleep";
+    private static final String TV_SEND_STANDBY_ON_SLEEP_ENABLED = "1";
+
     public HdmiCecTvStandbyTest() {
         super(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
     }
@@ -47,9 +50,6 @@
                     .around(CecRules.requiresDeviceType(this, HdmiCecConstants.CEC_DEVICE_TYPE_TV))
                     .around(hdmiCecClient);
 
-    private static final String HDMI_CONTROL_DEVICE_AUTO_OFF =
-            "hdmi_control_auto_device_off_enabled";
-
     /**
      * Test 11.1.3-1
      *
@@ -60,7 +60,8 @@
     public void cect_11_1_3_1_BroadcastStandby() throws Exception {
         ITestDevice device = getDevice();
         device.waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
-        boolean wasOn = setHdmiControlDeviceAutoOff(true);
+        String value = getSettingsValue(TV_SEND_STANDBY_ON_SLEEP);
+        setSettingsValue(TV_SEND_STANDBY_ON_SLEEP, TV_SEND_STANDBY_ON_SLEEP_ENABLED);
         try {
             sendDeviceToSleep();
             hdmiCecClient.checkExpectedOutput(LogicalAddress.BROADCAST, CecOperand.STANDBY);
@@ -70,19 +71,7 @@
                     .isEqualTo("mWakefulness=Asleep");
         } finally {
             wakeUpDevice();
-            setHdmiControlDeviceAutoOff(wasOn);
+            setSettingsValue(TV_SEND_STANDBY_ON_SLEEP, value);
         }
     }
-
-    private boolean setHdmiControlDeviceAutoOff(boolean turnOn) throws Exception {
-        ITestDevice device = getDevice();
-        String val =
-                device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF)
-                        .trim();
-        String valToSet = turnOn ? "1" : "0";
-        device.executeShellCommand(
-                "settings put global " + HDMI_CONTROL_DEVICE_AUTO_OFF + " " + valToSet);
-        device.executeShellCommand("settings get global " + HDMI_CONTROL_DEVICE_AUTO_OFF);
-        return val.equals("1");
-    }
 }
diff --git a/hostsidetests/incident/OWNERS b/hostsidetests/incident/OWNERS
index 37c0932..60e88f9 100644
--- a/hostsidetests/incident/OWNERS
+++ b/hostsidetests/incident/OWNERS
@@ -1,10 +1,9 @@
-# Bug component: 329246
+# Bug component: 366902
 jeffreyhuang@google.com
-joeo@google.com
 jreck@google.com
-kwekua@google.com
+jtnguyen@google.com
 muhammadq@google.com
-ruchirr@google.com
+sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yamasani@google.com
diff --git a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
index a756004..e959abd 100644
--- a/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/IncidentdTest.java
@@ -45,8 +45,6 @@
 
         DiskStatsProtoTest.verifyDiskStatsServiceDumpProto(dump.getDiskstats(), filterLevel, getDevice());
 
-        PackageIncidentTest.verifyPackageServiceDumpProto(dump.getPackage(), filterLevel);
-
         PowerIncidentTest.verifyPowerManagerServiceDumpProto(dump.getPower(), filterLevel);
 
         if (PrintProtoTest.supportsPrinting(getDevice())) {
diff --git a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
deleted file mode 100644
index 66137c1..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/PackageIncidentTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.cts;
-
-import android.service.pm.PackageProto;
-import android.service.pm.PackageProto.UserInfoProto;
-import android.service.pm.PackageServiceDumpProto;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/** Test for "dumpsys package --proto" */
-public class PackageIncidentTest extends ProtoDumpTestCase {
-    // Use the test apk from the BatteryStatsIncidentTest
-    private static final String DEVICE_SIDE_TEST_APK = "CtsBatteryStatsApp.apk";
-    private static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.device.batterystats";
-
-    @Override
-    protected void tearDown() throws Exception {
-        getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
-
-        super.tearDown();
-    }
-
-    private static void assertPositive(String name, long value) {
-        if (value > 0) return;
-        fail(name + " expected to be positive, but was: " + value);
-    }
-
-    private static void assertNotNegative(String name, long value) {
-        if (value >= 0) return;
-        fail(name + " expected to be zero or positive, but was: " + value);
-    }
-
-    /** Parse the output of "dumpsys package --proto" and make sure the values are probable. */
-    public void testPackageServiceDump() throws Exception {
-        final long st = System.currentTimeMillis();
-
-        installPackage(DEVICE_SIDE_TEST_APK, /* grantPermissions= */ true);
-
-        // Find the package UID, version code, and version string.
-        final Matcher matcher =
-                execCommandAndFind(
-                        "dumpsys package " + DEVICE_SIDE_TEST_PACKAGE,
-                        "userId=(\\d+).*versionCode=(\\d+).*versionName=([^\\n]*)",
-                        Pattern.DOTALL);
-        final int uid = Integer.parseInt(matcher.group(1));
-        final int versionCode = Integer.parseInt(matcher.group(2));
-        final String versionString = matcher.group(3).trim();
-
-        final PackageServiceDumpProto dump =
-                getDump(PackageServiceDumpProto.parser(), "dumpsys package --proto");
-
-        PackageProto testPackage = null;
-        for (PackageProto pkg : dump.getPackagesList()) {
-            if (pkg.getName().equals(DEVICE_SIDE_TEST_PACKAGE)) {
-                testPackage = pkg;
-                break;
-            }
-        }
-
-        assertNotNull(testPackage);
-        assertEquals(testPackage.getName(), DEVICE_SIDE_TEST_PACKAGE);
-        assertEquals(testPackage.getUid(), uid);
-        assertEquals(testPackage.getVersionCode(), versionCode);
-        assertEquals(testPackage.getVersionString(), versionString);
-        assertPositive("install_time_ms", testPackage.getInstallTimeMs());
-        assertEquals(testPackage.getInstallTimeMs(), testPackage.getUpdateTimeMs());
-        assertEquals(testPackage.getSplits(0).getName(), "base");
-        assertEquals(testPackage.getSplits(0).getRevisionCode(), 0);
-        assertNotNull(testPackage.getUserPermissionsList());
-
-        UserInfoProto testUser = testPackage.getUsers(0);
-        assertEquals(testUser.getId(), 0);
-        assertEquals(testUser.getInstallType(),
-                PackageProto.UserInfoProto.InstallType.FULL_APP_INSTALL);
-        assertFalse(testUser.getIsHidden());
-        assertFalse(testUser.getIsLaunched());
-        assertFalse(testUser.getEnabledState() == PackageProto.UserInfoProto
-                .EnabledState.COMPONENT_ENABLED_STATE_DISABLED_USER);
-
-        verifyPackageServiceDumpProto(dump, PRIVACY_NONE);
-    }
-
-    static void verifyPackageServiceDumpProto(PackageServiceDumpProto dump, final int filterLevel) throws Exception {
-        assertNotNull(dump.getVerifierPackage().getName());
-        assertNotNull(dump.getSharedLibraries(0).getName());
-        if (dump.getSharedLibraries(0).getIsJar()) {
-            assertNotNull(dump.getSharedLibraries(0).getPath());
-        } else {
-            assertNotNull(dump.getSharedLibraries(0).getApk());
-        }
-        assertNotNull(dump.getFeatures(0).getName());
-
-        PackageServiceDumpProto.SharedUserProto systemUser = null;
-        for (PackageServiceDumpProto.SharedUserProto user : dump.getSharedUsersList()) {
-            if (user.getUid() == 1000) {
-                systemUser = user;
-                break;
-            }
-        }
-        assertNotNull(systemUser);
-        assertEquals("android.uid.system", systemUser.getName());
-
-        if (filterLevel == PRIVACY_AUTO) {
-            for (String msg : dump.getMessagesList()) {
-                assertTrue(msg.isEmpty());
-            }
-        }
-    }
-}
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
index 4ad7237..a3774b8 100644
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/Android.bp
@@ -35,5 +35,5 @@
         "general-tests",
     ],
     sdk_version: "test_current",
-    min_sdk_version: "19",
+    min_sdk_version: "28",
 }
diff --git a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
index 58b9e70..4b5fa9c 100755
--- a/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/devicetest/AndroidManifest.xml
@@ -19,13 +19,6 @@
      package="android.inputmethodservice.cts.devicetest"
      android:targetSandboxVersion="2">
 
-    <!--
-              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-              latest OS behaviors.
-            -->
-    <uses-sdk android:minSdkVersion="28"
-         android:targetSdkVersion="28"/>
-
     <application android:label="CtsInputMethodServiceDeviceTests"
          android:icon="@mipmap/ic_launcher"
          android:allowBackup="false"
diff --git a/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
index faebb9f..0888db3 100755
--- a/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/edittextapp/AndroidManifest.xml
@@ -18,8 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.inputmethodservice.cts.edittextapp" android:targetSandboxVersion="2">
 
-    <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" />
-
     <application
         android:label="@string/app_name">
         <activity
diff --git a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
index b0eaaa7..cd1174e 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime1/AndroidManifest.xml
@@ -18,13 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.inputmethodservice.cts.ime1">
 
-    <!--
-              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-              latest OS behaviors.
-            -->
-    <uses-sdk android:minSdkVersion="19"
-         android:targetSdkVersion="25"/>
-
     <application android:label="@string/ime_name"
          android:allowBackup="false"
          android:theme="@android:style/Theme.InputMethod">
diff --git a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
index 0168b8d..09305df 100755
--- a/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/ime2/AndroidManifest.xml
@@ -18,13 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.inputmethodservice.cts.ime2">
 
-    <!--
-              TODO: We may need to have another new APK that has the latest targetSdkVersion to check the
-              latest OS behaviors.
-            -->
-    <uses-sdk android:minSdkVersion="19"
-         android:targetSdkVersion="25"/>
-
     <application android:label="@string/ime_name"
          android:allowBackup="false"
          android:theme="@android:style/Theme.InputMethod">
diff --git a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
index a8b17e3..ce79f74 100755
--- a/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
+++ b/hostsidetests/inputmethodservice/deviceside/provider/AndroidManifest.xml
@@ -18,9 +18,6 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.inputmethodservice.cts.provider">
 
-    <uses-sdk android:minSdkVersion="26"
-         android:targetSdkVersion="26"/>
-
     <application android:label="CtsInputMethodServiceEventProvider">
         <provider android:authorities="android.inputmethodservice.cts.provider"
              android:name="android.inputmethodservice.cts.provider.EventProvider"
@@ -28,7 +25,7 @@
         <receiver android:name="android.inputmethodservice.cts.receiver.EventReceiver"
              android:exported="true">
             <intent-filter>
-                <action android:name="android.inputmethodservice.cts.action.IME_EVENT"/>
+                <action android:name="android.inputmethodservice.cts.action.DEVICE_EVENT"/>
             </intent-filter>
         </receiver>
     </application>
diff --git a/hostsidetests/install/app/src/android/cts/install/InstallTest.java b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
index 4d9d835..711df3d 100644
--- a/hostsidetests/install/app/src/android/cts/install/InstallTest.java
+++ b/hostsidetests/install/app/src/android/cts/install/InstallTest.java
@@ -104,15 +104,12 @@
     @Test
     public void assert_commitFailure_phase() {
         Install install = getParameterizedInstall(VERSION_CODE_TARGET);
-        if (mEnableRollback) {
-            InstallUtils.commitExpectingFailure(IllegalArgumentException.class,
-                "Non-staged APEX session doesn't support INSTALL_ENABLE_ROLLBACK", install);
-        } else if (mInstallType.equals(INSTALL_TYPE.SINGLE_APEX)) {
+        if (mInstallType.equals(INSTALL_TYPE.SINGLE_APEX)) {
             InstallUtils.commitExpectingFailure(AssertionError.class,
                 "does not support non-staged update", install);
         } else {
-            InstallUtils.commitExpectingFailure(AssertionError.class,
-                "Non-staged multi package install of APEX and APK packages is not supported",
+            InstallUtils.commitExpectingFailure(IllegalStateException.class,
+                "Mix of APK and APEX is not supported for non-staged multi-package session",
                 install);
         }
     }
diff --git a/hostsidetests/locale/Android.bp b/hostsidetests/locale/Android.bp
new file mode 100644
index 0000000..910c1c1
--- /dev/null
+++ b/hostsidetests/locale/Android.bp
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsLocaleManagerHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "host-libprotobuf-java-full",
+        "platformprotos",
+    ],
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    data: [
+        ":CtsLocaleManagerTestDeviceApp",
+        ":CtsAtomTestApp",
+    ],
+}
diff --git a/hostsidetests/locale/AndroidTest.xml b/hostsidetests/locale/AndroidTest.xml
new file mode 100644
index 0000000..64a0426
--- /dev/null
+++ b/hostsidetests/locale/AndroidTest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS LocaleManager host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- Module cannot run in secondary user because these tests require a device restart which
+         automatically switches back to the primary user. Switching to the secondary user within the
+         test still causes the user to remain in LOCKED state which means the tests don't work.
+
+         Also note that most of our API test cases are in our instrumentation CTS suite at
+         cts/tests/framework/base/locale, which does in fact work across secondary users.
+    -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsLocaleManagerTestDeviceApp.apk" />
+        <option name="test-file-name" value="CtsAtomTestApp.apk" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsLocaleManagerHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/locale/OWNERS b/hostsidetests/locale/OWNERS
new file mode 100644
index 0000000..9d41159
--- /dev/null
+++ b/hostsidetests/locale/OWNERS
@@ -0,0 +1,2 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+include /tests/framework/base/locale/OWNERS
diff --git a/hostsidetests/locale/TEST_MAPPING b/hostsidetests/locale/TEST_MAPPING
new file mode 100644
index 0000000..60a24a4
--- /dev/null
+++ b/hostsidetests/locale/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLocaleManagerHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/locale/app/Android.bp b/hostsidetests/locale/app/Android.bp
new file mode 100644
index 0000000..d8ed825
--- /dev/null
+++ b/hostsidetests/locale/app/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsLocaleManagerTestDeviceApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+}
diff --git a/hostsidetests/locale/app/AndroidManifest.xml b/hostsidetests/locale/app/AndroidManifest.xml
new file mode 100755
index 0000000..cf50f13
--- /dev/null
+++ b/hostsidetests/locale/app/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.localemanager.app">
+
+    <application>
+        <activity android:name=".MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java b/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java
new file mode 100644
index 0000000..54a0210
--- /dev/null
+++ b/hostsidetests/locale/app/src/android/localemanager/app/MainActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.localemanager.app;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * A simple activity which doesn't do anything interesting, but needs to be installed so that we can
+ * set its app-specific locales.
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+    }
+
+}
diff --git a/hostsidetests/locale/atom/app/Android.bp b/hostsidetests/locale/atom/app/Android.bp
new file mode 100644
index 0000000..b14b97be
--- /dev/null
+++ b/hostsidetests/locale/atom/app/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAtomTestApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "system_current",
+}
diff --git a/hostsidetests/locale/atom/app/AndroidManifest.xml b/hostsidetests/locale/atom/app/AndroidManifest.xml
new file mode 100755
index 0000000..ec95e96
--- /dev/null
+++ b/hostsidetests/locale/atom/app/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT 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.localemanager.atom.app">
+
+    <application>
+        <activity android:name=".ActivityForSettingLocalesOfAnotherApp"
+             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=".ActivityForNullCheckForInputLocales"
+                  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=".ActivityForNullCheckForInputPackageName"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>
diff --git a/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java
new file mode 100644
index 0000000..2908ec9
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputLocales.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+
+/**
+ * Activity which tries to apply null locales on the application when invoked.
+ */
+public class ActivityForNullCheckForInputLocales extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        callSetApplicationLocalesWithNullLocales();
+    }
+
+    public void callSetApplicationLocalesWithNullLocales() {
+        // This function is to verify that the service throws an exception when null target
+        // packageName is passed, and this scenario is logged with a failure in the
+        // ApplicationLocalesChangedAtom.
+        LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+        try {
+            mLocaleManager.setApplicationLocales(null);
+        } catch (NullPointerException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java
new file mode 100644
index 0000000..8852754
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForNullCheckForInputPackageName.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+/**
+ * Activity which tries to call setApplicationLocales with null packageName when invoked.
+ */
+public class ActivityForNullCheckForInputPackageName extends Activity {
+    private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        callSetApplicationLocalesWithNullPackage();
+    }
+
+    public void callSetApplicationLocalesWithNullPackage() {
+        // This function is to verify that the service throws an exception when null target
+        // packageName is passed, and this scenario is logged with a failure in the
+        // ApplicationLocalesChangedAtom.
+        LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+        try {
+            mLocaleManager.setApplicationLocales(null,
+                    LocaleList.forLanguageTags(DEFAULT_LANGUAGE_TAGS));
+        } catch (NullPointerException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java
new file mode 100644
index 0000000..8f4c2de
--- /dev/null
+++ b/hostsidetests/locale/atom/app/src/android/localemanager/atom/app/ActivityForSettingLocalesOfAnotherApp.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.atom.app;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+/**
+ * Activity which calls setApplicationLocales() for another application when invoked.
+ */
+public class ActivityForSettingLocalesOfAnotherApp extends Activity {
+    private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+    private static final String TEST_APP_PACKAGE_NAME = "android.localemanager.app";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setLocalesForOtherApplication();
+    }
+
+    public void setLocalesForOtherApplication() {
+        LocaleManager mLocaleManager = this.getSystemService(LocaleManager.class);
+        try {
+            mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE_NAME,
+                    LocaleList.forLanguageTags(DEFAULT_LANGUAGE_TAGS));
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java b/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java
new file mode 100644
index 0000000..9124ab7
--- /dev/null
+++ b/hostsidetests/locale/src/android/localemanager/cts/ApplicationLocalesChangedAtomTests.java
@@ -0,0 +1,269 @@
+/*
+ * 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.localemanager.cts;
+
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+
+import java.util.List;
+
+public class ApplicationLocalesChangedAtomTests extends DeviceTestCase implements IBuildReceiver {
+    public static final String ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_PACKAGE_NAME =
+            "ActivityForNullCheckForInputPackageName";
+    public static final String ACTIVITY_FOR_SETTING_LOCALES_OF_ANOTHER_APP =
+            "ActivityForSettingLocalesOfAnotherApp";
+    public static final String ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_LOCALES =
+            "ActivityForNullCheckForInputLocales";
+    private int mShellUid;
+
+    private static final String INSTALLED_PACKAGE_NAME_APP1 = "android.localemanager.app";
+    private static final String INSTALLED_PACKAGE_NAME_APP2 = "android.localemanager.atom.app";
+    private static final String INVALID_PACKAGE_NAME = "invalid.package.name";
+
+    private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+    private static final String DEFAULT_LANGUAGE_TAGS_2 = "hi-IN,es-ES";
+    private static final String EMPTY_LANGUAGE_TAGS = "";
+    private static final int INVALID_UID = -1;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        resetAppLocales();
+        ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.APPLICATION_LOCALES_CHANGED_FIELD_NUMBER);
+
+        // This will be ROOT_UID if adb is running as root, SHELL_UID otherwise.
+        mShellUid = DeviceUtils.getHostUid(getDevice());
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+    }
+
+    public void testAtomLogging_newConfiguration_logsAtomSuccessfully()
+            throws Exception {
+        // executing API to change locales of the installed application, this should trigger an
+        // ApplicationLocalesChanged atom entry to be logged.
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+
+        // Retrieving logged metric entries and asserting if they are as expected.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertEquals(1, data.size());
+        AtomsProto.ApplicationLocalesChanged result = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(mShellUid,
+                DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+                /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+                AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result);
+
+        // executing API to change locales of the installed application, this should trigger an
+        // ApplicationLocalesChanged atom entry to be logged.
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS_2);
+
+        List<StatsLog.EventMetricData> data2 = ReportUtils.getEventMetricDataList(getDevice());
+        assertEquals(1, data.size());
+        AtomsProto.ApplicationLocalesChanged result2 = data2.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(mShellUid,
+                DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+                DEFAULT_LANGUAGE_TAGS, DEFAULT_LANGUAGE_TAGS_2,
+                AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result2);
+    }
+
+    public void testAtomLogging_invalidPackage_logsAtomWithFailureInvalidPackageName()
+            throws Exception {
+        // calling setApplicationLocales() with an invalid package name.
+        executeSetApplicationLocalesCommand(INVALID_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+
+        // retrieving logged metric data
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // assert data was logged.
+        assertEquals(1, data.size());
+        AtomsProto.ApplicationLocalesChanged result = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        // The input package name is invalid therefore the status should be:
+        // FAILURE_INVALID_TARGET_PACKAGE
+        verifyAtomDetails(mShellUid,
+                INVALID_UID, /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+                AtomsProto.ApplicationLocalesChanged.Status.FAILURE_INVALID_TARGET_PACKAGE,
+                result);
+    }
+
+    public void testAtomLogging_permissionAbsent_logsAtomWithFailurePermissionAbsent()
+            throws Exception {
+        // For the purpose of testing the failure case of "Permission Absent" we required one app
+        // (without the CHANGE_CONFIGURATION permission) to call
+        // LocaleManager#setApplicationLocales() for another application so that this call fails in
+        // a Security Exception. To replicate this scenario, SetApplicationLocales() was called
+        // from the MainActivity of app2, attempting to change locales of app1. When
+        // app2/MainActivity is invoked this failure test case gets recorded.
+        invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_SETTING_LOCALES_OF_ANOTHER_APP);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertEquals(1, data.size());
+
+        AtomsProto.ApplicationLocalesChanged result = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+                DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+                /* expectedPreviousLocales= */ "", DEFAULT_LANGUAGE_TAGS,
+                AtomsProto.ApplicationLocalesChanged.Status.FAILURE_PERMISSION_ABSENT, result);
+    }
+
+    public void testAtomLogging_inputLocalesNull_logsAtomWithFailure()
+            throws Exception {
+        // For the purpose of testing the failure case of "null locales" we need an application
+        // to call setApplicationLocales() with null locales as input. To replicate this
+        // scenario, SetApplicationLocales() was called indirectly
+        // from the onCreate() of app2.ActivityForNullCheckForInputLocales with null input locales.
+        invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_LOCALES);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertEquals(1, data.size());
+
+        AtomsProto.ApplicationLocalesChanged result = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+                INVALID_UID, /* expectedPreviousLocales= */ "",
+                /* expectedNewLocales= */ "",
+                AtomsProto.ApplicationLocalesChanged.Status.STATUS_UNSPECIFIED, result);
+    }
+
+    public void testAtomLogging_nullPackageName_logsAtomWithFailure()
+            throws Exception {
+        // For the purpose of testing the failure case of "null PackageName" we need one application
+        // to call setApplicationLocales() with null packageName as input. To replicate this
+        // scenario, SetApplicationLocales() was called indirectly
+        // from the onCreate() of app2.ActivityForNullCheckForInputPackageName with null input
+        // package.
+        invokeActivityInApp2AndDestroyIt(ACTIVITY_FOR_NULL_CHECK_FOR_INPUT_PACKAGE_NAME);
+
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertEquals(1, data.size());
+
+        AtomsProto.ApplicationLocalesChanged result = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP2),
+                INVALID_UID, /* expectedPreviousLocales= */ "",
+                /* expectedNewLocales= */ "",
+                AtomsProto.ApplicationLocalesChanged.Status.STATUS_UNSPECIFIED, result);
+    }
+
+    public void testAtomLogging_noConfigChange_logsAtomWithConfigUncommitted()
+            throws Exception {
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+        // same command called twice to replicate the case of no commit as previous config is
+        // same as current requested.
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, DEFAULT_LANGUAGE_TAGS);
+
+        // fetching metric data.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        // assert: atom was logged twice
+        assertEquals(2, data.size());
+
+        // assert: expected config for the first call
+        AtomsProto.ApplicationLocalesChanged result1 = data.get(0)
+                .getAtom().getApplicationLocalesChanged();
+        verifyAtomDetails(mShellUid,
+                DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+                /* expectedPreviousLocales= */"", DEFAULT_LANGUAGE_TAGS,
+                AtomsProto.ApplicationLocalesChanged.Status.CONFIG_COMMITTED, result1);
+
+        // assert: expected config for the second call
+        AtomsProto.ApplicationLocalesChanged result2 = data.get(1)
+                .getAtom().getApplicationLocalesChanged();
+
+        // previous locales are same as new one, therefore status should be: CONFIG_UNCOMMITTED
+        verifyAtomDetails(mShellUid,
+                DeviceUtils.getAppUid(getDevice(), INSTALLED_PACKAGE_NAME_APP1),
+                DEFAULT_LANGUAGE_TAGS, DEFAULT_LANGUAGE_TAGS,
+                AtomsProto.ApplicationLocalesChanged.Status.CONFIG_UNCOMMITTED, result2);
+    }
+
+    private void resetAppLocales() throws Exception {
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP1, EMPTY_LANGUAGE_TAGS);
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME_APP2, EMPTY_LANGUAGE_TAGS);
+    }
+
+
+    private void executeSetApplicationLocalesCommand(String packageName, String languageTags)
+            throws Exception {
+        getDevice().executeShellCommand(
+                String.format(
+                        "cmd locale set-app-locales %s --user 0 --locales %s",
+                        packageName,
+                        languageTags
+                )
+        );
+    }
+
+    private void verifyAtomDetails(int expectedCallingUid, int expectedTargetUid,
+            String expectedPreviousLocales, String expectedNewLocales,
+            AtomsProto.ApplicationLocalesChanged.Status expectedStatus,
+            AtomsProto.ApplicationLocalesChanged result) {
+        assertEquals(expectedCallingUid, result.getCallingUid());
+        assertEquals(expectedTargetUid, result.getTargetUid());
+        assertEquals(expectedPreviousLocales, result.getPrevLocales());
+        assertEquals(expectedNewLocales, result.getNewLocales());
+        assertEquals(expectedStatus, result.getStatus());
+    }
+
+    private void invokeActivityInApp2AndDestroyIt(String activityName) {
+        String activity = INSTALLED_PACKAGE_NAME_APP2 + "/." + activityName;
+        try {
+            // launch the activity
+            getDevice().executeShellCommand(
+                    String.format(
+                            "am start -W %s ",
+                            activity
+                    )
+            );
+        } catch (Exception e) {
+            // DO nothing.
+        }
+        // destroy the app.
+        try {
+            // force stop the application
+            getDevice().executeShellCommand(
+                    String.format(
+                            "am kill -W %s", INSTALLED_PACKAGE_NAME_APP2
+                    )
+            );
+        } catch (Exception e) {
+            // DO nothing.
+        }
+    }
+}
diff --git a/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java b/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java
new file mode 100644
index 0000000..1422195
--- /dev/null
+++ b/hostsidetests/locale/src/android/localemanager/cts/LocaleManagerHostTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.localemanager.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IDeviceTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test to check that {@link android.app.LocaleManager} APIs persist across device restarts.
+ *
+ * <p>When this test builds, it also builds {@link android.localemanager.app.MainActivity} into an
+ * APK which is then installed at runtime. The Activity does not do anything interesting, but we
+ * need it to be installed so that we can set an app-specific locale for it's package.
+ *
+ * <p><b>Note:</b> A more comprehensive set of CTS cases are included in the instrumentation suite
+ * at {@link android.localemanager.cts.LocaleManagerTests}.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LocaleManagerHostTest implements IDeviceTest {
+
+    private static final String TAG = LocaleManagerHostTest.class.getSimpleName();
+
+    private static final String INSTALLED_PACKAGE_NAME = "android.localemanager.app";
+
+    private static final String DEFAULT_LANGUAGE_TAGS = "hi-IN,de-DE";
+    private static final String EMPTY_LANGUAGE_TAGS = "";
+
+    private static final String GET_APP_LOCALES_SHELL_OUTPUT_FORMAT =
+            "Locales for %s for user 0 are [%s]\n";
+    private static final String DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT =
+            String.format(GET_APP_LOCALES_SHELL_OUTPUT_FORMAT,
+                    INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+    private static final String EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT =
+            String.format(GET_APP_LOCALES_SHELL_OUTPUT_FORMAT,
+                    INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+
+    private ITestDevice mDevice;
+
+    @Override
+    public void setDevice(ITestDevice device) {
+        mDevice = device;
+    }
+
+    @Override
+    public ITestDevice getDevice() {
+        return mDevice;
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        ITestDevice device = getDevice();
+        assertNotNull("Device not set", device);
+        resetAppLocales();
+    }
+
+    @Test
+    public void testSetApplicationLocale_nonEmptyLocales_persistsAcrossReboots() throws Exception {
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+        String appLocalesBeforeRestart =
+                executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+        assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBeforeRestart);
+
+        restartDeviceAndWaitUntilReady();
+
+        // Verify locales still equal after restart
+        String appLocalesAfterRestart = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+        assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesAfterRestart);
+    }
+
+    @Test
+    public void testSetApplicationLocale_emptyLocales_persistsAcrossReboots() throws Exception {
+        // set some application locales to start with
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, DEFAULT_LANGUAGE_TAGS);
+        String appLocalesBase = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+        assertEquals(DEFAULT_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBase);
+
+        // reset the application locales to empty
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+        String appLocalesBeforeRestart =
+                executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+        assertEquals(EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesBeforeRestart);
+
+        restartDeviceAndWaitUntilReady();
+        // Verify new empty locales persisted after restart
+        String appLocalesAfterRestart = executeGetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME);
+        assertEquals(EMPTY_LANGUAGE_TAGS_GET_APP_LOCALES_SHELL_OUTPUT, appLocalesAfterRestart);
+    }
+
+    private void restartDeviceAndWaitUntilReady() throws Exception {
+        // Flush pending writes before rebooting device
+        getDevice().executeShellCommand("am write");
+        getDevice().reboot();
+    }
+
+
+    private void resetAppLocales() throws Exception {
+        executeSetApplicationLocalesCommand(INSTALLED_PACKAGE_NAME, EMPTY_LANGUAGE_TAGS);
+    }
+
+
+    private void executeSetApplicationLocalesCommand(String packageName, String languageTags)
+            throws Exception {
+        getDevice().executeShellCommand(
+                String.format(
+                        "cmd locale set-app-locales %s --user 0 --locales %s",
+                        packageName,
+                        languageTags
+                )
+        );
+    }
+
+    private String executeGetApplicationLocalesCommand(String packageName) throws Exception {
+        return getDevice().executeShellCommand(
+                String.format(
+                        "cmd locale get-app-locales %s --user 0",
+                        packageName
+                )
+        );
+    }
+
+}
diff --git a/hostsidetests/media/OWNERS b/hostsidetests/media/OWNERS
index e72446f..890f7d2 100644
--- a/hostsidetests/media/OWNERS
+++ b/hostsidetests/media/OWNERS
@@ -1,8 +1,12 @@
 # Bug component: 1344
 elaurent@google.com
+gyumin@google.com
+hdmoon@google.com
+jaewan@google.com
+jinpark@google.com
+klhyun@google.com
 lajos@google.com
 sungsoo@google.com
-jaewan@google.com
 
 # go/android-fwk-media-solutions for info on areas of ownership.
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
index 75a4c1e..026a1b1 100644
--- a/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
+++ b/hostsidetests/media/app/MediaExtractorTest/src/android/media/cts/MediaExtractorDeviceSideTest.java
@@ -56,8 +56,9 @@
                 InstrumentationRegistry.getInstrumentation().getContext().getAssets();
         try (AssetFileDescriptor fileDescriptor = assetManager.openFd(SAMPLE_PATH)) {
             mediaExtractor.setDataSource(fileDescriptor);
+        } finally {
+            mediaExtractor.release();
         }
-        mediaExtractor.release();
     }
 
     @Test
diff --git a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
index f0c8a9b..78b126c 100644
--- a/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
+++ b/hostsidetests/media/src/android/media/cts/MediaExtractorHostSideTest.java
@@ -149,7 +149,7 @@
     }
 
     /**
-     * Returns all MediaParser reported metric events sorted by timestamp.
+     * Asserts that a single entry point has been reported by MediaMetrics and returns it.
      *
      * <p>Note: Calls {@link #getAndClearReportList()} to obtain the statsd report.
      */
diff --git a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
index 02a49da..87285e8 100644
--- a/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
+++ b/hostsidetests/media/src/android/media/metrics/cts/MediaMetricsAtomTests.java
@@ -29,9 +29,7 @@
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -534,7 +532,7 @@
         List<Set<Integer>> directionList = Arrays.asList(directionSet);
 
         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        AtomTestUtils.assertStatesOccurred(directionList, data, 0,
+        AtomTestUtils.assertStatesOccurredInOrder(directionList, data, 0,
                 atom -> atom.getMediametricsAaudiostreamReported().getDirection().getNumber());
 
         for (StatsLog.EventMetricData event : data) {
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
index a8a12af..91ac046 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/EphemeralTest.java
@@ -93,35 +93,37 @@
     }
 
     /**
-     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} immediately
+     * Test to verify that
+     * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} immediately
      * removes a user that isn't running.
-     *
-     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     * <p>
+     * Indirectly executed by means of the --set-ephemeral-if-in-use flag
      */
     @Presubmit
     @Test
-    public void testRemoveUserOrSetEphemeral_nonRunningUserRemoved() throws Exception {
+    public void testRemoveUserWhenPossible_nonRunningUserRemoved() throws Exception {
         final int userId = createUser();
 
-        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+        executeRemoveUserWhenPossibleAdbCommand(userId);
 
         assertUserNotPresent(userId);
     }
 
     /**
-     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
-     * current user to ephemeral and removes the user after user switch.
-     *
-     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     * Test to verify that
+     * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} sets the current
+     * user to ephemeral and removes the user after user switch.
+     * <p>
+     * Indirectly executed by means of the --set-ephemeral-if-in-use flag
      */
     @Presubmit
     @Test
-    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterSwitch()
+    public void testRemoveUserWhenPossible_currentUserSetEphemeral_removeAfterSwitch()
             throws Exception {
         final int userId = createUser();
 
         assertSwitchToNewUser(userId);
-        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+        executeRemoveUserWhenPossibleAdbCommand(userId);
         assertUserEphemeral(userId);
 
         assertSwitchToUser(userId, mInitialUserId);
@@ -130,26 +132,27 @@
     }
 
     /**
-     * Test to verify that {@link android.os.UserManager#removeUserOrSetEphemeral(int)} sets the
-     * current user to ephemeral and removes that user after reboot.
-     *
-     * <p>Indirectly executed by means of the --set-ephemeral-if-in-use flag
+     * Test to verify that
+     * {@link android.os.UserManager#removeUserWhenPossible(UserHandle, boolean)} sets the current
+     * user to ephemeral and removes that user after reboot.
+     * <p>
+     * Indirectly executed by means of the --set-ephemeral-if-in-use flag
      */
     @Presubmit
     @Test
-    public void testRemoveUserOrSetEphemeral_currentUserSetEphemeral_removeAfterReboot()
+    public void testRemoveUserWhenPossible_currentUserSetEphemeral_removeAfterReboot()
             throws Exception {
         final int userId = createUser();
 
         assertSwitchToNewUser(userId);
-        executeRemoveUserOrSetEphemeralAdbCommand(userId);
+        executeRemoveUserWhenPossibleAdbCommand(userId);
         assertUserEphemeral(userId);
 
         getDevice().reboot();
         assertUserNotPresent(userId);
     }
 
-    private void executeRemoveUserOrSetEphemeralAdbCommand(int userId) throws Exception {
+    private void executeRemoveUserWhenPossibleAdbCommand(int userId) throws Exception {
         getDevice().executeShellV2Command("pm remove-user --set-ephemeral-if-in-use " + userId);
     }
 
diff --git a/hostsidetests/os/AndroidTest.xml b/hostsidetests/os/AndroidTest.xml
index 1eb77ac..d0a869c 100644
--- a/hostsidetests/os/AndroidTest.xml
+++ b/hostsidetests/os/AndroidTest.xml
@@ -20,6 +20,17 @@
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/hostside/os" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts"/>
+    </target_preparer>
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsStaticSharedLibProviderApp1.apk->/data/local/tmp/cts/hostside/os/CtsStaticSharedLibProviderApp1.apk" />
+        <option name="push" value="CtsStaticSharedLibProviderApp2.apk->/data/local/tmp/cts/hostside/os/CtsStaticSharedLibProviderApp2.apk" />
+    </target_preparer>
+
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDeviceOsTestApp.apk" />
diff --git a/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
index e117d14..1e04774 100644
--- a/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
+++ b/hostsidetests/os/src/android/os/cts/InattentiveSleepTests.java
@@ -40,15 +40,20 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class InattentiveSleepTests extends BaseHostJUnit4Test {
     private static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
-    private static final String PACKAGE_NAME = "android.os.inattentivesleeptests";
     private static final String APK_NAME = "CtsInattentiveSleepTestApp.apk";
 
     private static final long TIME_BEFORE_WARNING_MS = 1200L;
 
     private static final String CMD_DUMPSYS_POWER = "dumpsys power --proto";
     private static final String WARNING_WINDOW_TOKEN_TITLE = "InattentiveSleepWarning";
-    private static final String CMD_START_APP_TEMPLATE =
-            "am start -W -a android.intent.action.MAIN -p %s -c android.intent.category.LAUNCHER";
+    private static final String ACTIVITY_KEEP_SCREEN_ON = ".KeepScreenOnActivity";
+    private static final String SERVICE_PARTIAL_WAKE_LOCK = ".PartialWakeLockService";
+    private static final String CMD_START_ACTIVITY_TEMPLATE =
+            "am start -W android.os.inattentivesleeptests/%s";
+    private static final String CMD_START_FG_SERVICE_TEMPLATE =
+            "am start-foreground-service android.os.inattentivesleeptests/%s";
+    private static final String CMD_STOP_SERVICE_TEMPLATE =
+            "am stop-service android.os.inattentivesleeptests/%s";
 
     private static final String CMD_GET_STAY_ON = "settings get global stay_on_while_plugged_in";
     private static final String CMD_PUT_STAY_ON_TEMPLATE =
@@ -81,12 +86,20 @@
         mWarningDurationConfig = getWarningDurationConfig();
         mOriginalStayOnSetting = Long.parseLong(
                 mDevice.executeShellCommand(CMD_GET_STAY_ON).trim());
+
+        installPackage(APK_NAME);
+
+        // Prevent the device from suspending while screen is off
+        startPartialWakeLockService();
+
         mDevice.executeShellCommand(CMD_DISABLE_STAY_ON);
         setInattentiveSleepTimeout(TIME_BEFORE_WARNING_MS + mWarningDurationConfig);
     }
 
     @After
     public void tearDown() throws Exception {
+        wakeUp();
+        stopPartialWakeLockService();
         mDevice.executeShellCommand(
                 String.format(CMD_PUT_STAY_ON_TEMPLATE, mOriginalStayOnSetting));
         mDevice.executeShellCommand(CMD_DELETE_TIMEOUT_SETTING);
@@ -101,9 +114,19 @@
         mDevice.executeShellCommand(CMD_KEYEVENT_WAKEUP);
     }
 
+    private void startPartialWakeLockService() throws Exception {
+        mDevice.executeShellCommand(
+                String.format(CMD_START_FG_SERVICE_TEMPLATE, SERVICE_PARTIAL_WAKE_LOCK));
+    }
+
+    private void stopPartialWakeLockService() throws Exception {
+        mDevice.executeShellCommand(
+                String.format(CMD_STOP_SERVICE_TEMPLATE, SERVICE_PARTIAL_WAKE_LOCK));
+    }
+
     private void startKeepScreenOnActivity() throws Exception {
-        installPackage(APK_NAME);
-        mDevice.executeShellCommand(String.format(CMD_START_APP_TEMPLATE, PACKAGE_NAME));
+        mDevice.executeShellCommand(
+                String.format(CMD_START_ACTIVITY_TEMPLATE, ACTIVITY_KEEP_SCREEN_ON));
     }
 
     private void setInattentiveSleepTimeout(long timeoutMs) throws Exception {
diff --git a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
index 360d8d7..39d6df0 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -16,10 +16,16 @@
 
 package android.os.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
+import android.platform.test.annotations.LargeTest;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.build.IBuildInfo;
@@ -30,10 +36,16 @@
 import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
 
 import java.io.FileNotFoundException;
+import java.util.Arrays;
 import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
 
+@Presubmit
 public class StaticSharedLibsHostTests extends DeviceTestCase implements IBuildReceiver {
     private static final String ANDROID_JUNIT_RUNNER_CLASS =
             "androidx.test.runner.AndroidJUnitRunner";
@@ -43,6 +55,9 @@
     private static final String STATIC_LIB_PROVIDER_RECURSIVE_PKG =
             "android.os.lib.provider.recursive";
 
+    private static final String STATIC_LIB_PROVIDER_RECURSIVE_NAME = "foo.bar.lib.recursive";
+    private static final String STATIC_LIB_PROVIDER_NAME = "foo.bar.lib";
+
     private static final String STATIC_LIB_PROVIDER1_APK = "CtsStaticSharedLibProviderApp1.apk";
     private static final String STATIC_LIB_PROVIDER1_PKG = "android.os.lib.provider";
 
@@ -88,6 +103,15 @@
     private static final String STATIC_LIB_NATIVE_CONSUMER_PKG
             = "android.os.lib.consumer";
 
+    private static final String STATIC_LIB_TEST_APP_PKG = "android.os.lib.app";
+    private static final String STATIC_LIB_TEST_APP_CLASS_NAME = STATIC_LIB_TEST_APP_PKG
+            + ".StaticSharedLibsTests";
+
+    private static final String SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD =
+            "unused_static_shared_lib_min_cache_period";
+
+    private static final long DEFAULT_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(15);
+
     private CompatibilityBuildHelper mBuildHelper;
     private boolean mInstantMode = false;
 
@@ -683,8 +707,7 @@
 
     @AppModeFull(reason = "Instant app cannot get package installer service")
     public void testCannotSamegradeStaticSharedLibByInstaller() throws Exception {
-        runDeviceTests("android.os.lib.app",
-                "android.os.lib.app.StaticSharedLibsTests",
+        runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
                 "testSamegradeStaticSharedLibFail");
     }
 
@@ -720,6 +743,111 @@
         }
     }
 
+    @LargeTest
+    @AppModeFull
+    public void testPruneUnusedStaticSharedLibraries_reboot_fullMode()
+            throws Exception {
+        doTestPruneUnusedStaticSharedLibraries_reboot();
+    }
+
+    @LargeTest
+    @AppModeInstant
+    public void testPruneUnusedStaticSharedLibraries_reboot_instantMode()
+            throws Exception {
+        mInstantMode = true;
+        doTestPruneUnusedStaticSharedLibraries_reboot();
+    }
+
+    private void doTestPruneUnusedStaticSharedLibraries_reboot()
+            throws Exception {
+        getDevice().uninstallPackage(STATIC_LIB_CONSUMER3_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER7_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        try {
+            // Install an unused library
+            assertThat(install(STATIC_LIB_PROVIDER_RECURSIVE_APK)).isNull();
+            assertThat(checkLibrary(STATIC_LIB_PROVIDER_RECURSIVE_NAME)).isTrue();
+
+            // Install the client and the corresponding library
+            assertThat(install(STATIC_LIB_PROVIDER7_APK)).isNull();
+            assertThat(install(STATIC_LIB_CONSUMER3_APK)).isNull();
+            assertThat(checkLibrary(STATIC_LIB_PROVIDER_NAME)).isTrue();
+
+            // Disallow to cache static shared library
+            setGlobalSetting(SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD,
+                    Integer.toString(0));
+
+            // TODO(205779832): There's a maximum two-seconds-delay before SettingsProvider persists
+            //  the settings. Waits for 3 seconds before reboot the device to ensure the setting is
+            //  persisted.
+            Thread.sleep(3_000);
+            getDevice().reboot();
+
+            // Waits for the uninstallation of the unused library to ensure the job has be executed
+            // correctly.
+            PollingCheck.check("Library " + STATIC_LIB_PROVIDER_RECURSIVE_NAME
+                            + " should be uninstalled", DEFAULT_TIMEOUT_MILLIS,
+                    () -> !checkLibrary(STATIC_LIB_PROVIDER_RECURSIVE_NAME));
+            assertWithMessage(
+                    "Library " + STATIC_LIB_PROVIDER_NAME + " should not be uninstalled")
+                    .that(checkLibrary(STATIC_LIB_PROVIDER_NAME)).isTrue();
+        } finally {
+            setGlobalSetting(SETTING_UNUSED_STATIC_SHARED_LIB_MIN_CACHE_PERIOD, null);
+            getDevice().uninstallPackage(STATIC_LIB_CONSUMER3_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER7_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        }
+    }
+
+    @LargeTest
+    @AppModeFull
+    public void testInstallStaticSharedLib_notKillDependentApp() throws Exception {
+        getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        try {
+            // Install library dependency
+            assertNull(install(STATIC_LIB_PROVIDER_RECURSIVE_APK));
+            // Install the first library
+            assertNull(install(STATIC_LIB_PROVIDER1_APK));
+            // Install the client
+            assertNull(install(STATIC_LIB_CONSUMER1_APK));
+
+            // Bind the service in consumer1 app to verify that the app should not be killed when
+            // a new version static shared library installed.
+            runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
+                    "testInstallStaticSharedLib_notKillDependentApp");
+        } finally {
+            getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        }
+    }
+
+    @AppModeFull
+    public void testSamegradeStaticSharedLib_killDependentApp() throws Exception {
+        getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+        getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        try {
+            // Install library dependency
+            assertNull(install(STATIC_LIB_PROVIDER_RECURSIVE_APK));
+            // Install the first library
+            assertNull(install(STATIC_LIB_PROVIDER1_APK));
+            // Install the client
+            assertNull(install(STATIC_LIB_CONSUMER1_APK));
+
+            // Bind the service in consumer1 app to verify that the app should be killed when
+            // the static shared library is re-installed.
+            runDeviceTests(STATIC_LIB_TEST_APP_PKG, STATIC_LIB_TEST_APP_CLASS_NAME,
+                    "testSamegradeStaticSharedLib_killDependentApp");
+        } finally {
+            getDevice().uninstallPackage(STATIC_LIB_CONSUMER1_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER1_PKG);
+            getDevice().uninstallPackage(STATIC_LIB_PROVIDER_RECURSIVE_PKG);
+        }
+    }
+
     private String install(String apk) throws DeviceNotAvailableException, FileNotFoundException {
         return install(apk, false);
     }
@@ -728,4 +856,32 @@
         return getDevice().installPackage(mBuildHelper.getTestFile(apk), reinstall, false,
                 apk.contains("consumer") && mInstantMode ? "--instant" : "");
     }
+
+    private boolean checkLibrary(String libName) throws DeviceNotAvailableException {
+        final CommandResult result = getDevice().executeShellV2Command("pm list libraries");
+        if (result.getStatus() != CommandStatus.SUCCESS) {
+            fail("Failed to execute shell command: pm list libraries");
+        }
+        return Arrays.stream(result.getStdout().split("\n"))
+                .map(line -> line.split(":")[1])
+                .collect(Collectors.toList()).contains(libName);
+    }
+
+    private void setGlobalSetting(String key, String value) throws DeviceNotAvailableException {
+        final boolean deleteKey = (value == null);
+        final StringBuilder cmd = new StringBuilder("settings ");
+        if (deleteKey) {
+            cmd.append("delete ");
+        } else {
+            cmd.append("put ");
+        }
+        cmd.append("global ").append(key);
+        if (!deleteKey) {
+            cmd.append(" ").append(value);
+        }
+        final CommandResult res = getDevice().executeShellV2Command(cmd.toString());
+        if (res.getStatus() != CommandStatus.SUCCESS) {
+            fail("Failed to execute shell command: " + cmd);
+        }
+    }
 }
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
index 3588a14..5994c7f 100755
--- a/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/AndroidManifest.xml
@@ -18,7 +18,11 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.os.inattentivesleeptests"
      android:targetSandboxVersion="2">
+    <uses-permission android:name="android.permission.WAKE_LOCK" />
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
     <application>
+        <service android:name=".PartialWakeLockService"
+             android:exported="true" />
         <activity android:name=".KeepScreenOnActivity"
              android:exported="true">
             <intent-filter>
diff --git a/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java
new file mode 100644
index 0000000..d64fa84
--- /dev/null
+++ b/hostsidetests/os/test-apps/InattentiveSleepTestApp/src/android/os/inattentivesleeptests/PartialWakeLockService.java
@@ -0,0 +1,75 @@
+/*
+ * 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.os.inattentivesleeptests;
+
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.PowerManager;
+
+/** Service that holds a partial wakelock until stopped */
+public class PartialWakeLockService extends Service {
+    private static final String TAG = "PartialWakeLockService";
+    private static final String NOTIFICATION_CHANNEL_ID = "Inattentive Sleep Tests";
+
+    private PowerManager.WakeLock mWakeLock;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        NotificationManager notificationManager = getSystemService(NotificationManager.class);
+        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID,
+                NOTIFICATION_CHANNEL_ID, NotificationManager.IMPORTANCE_LOW);
+        notificationManager.createNotificationChannel(channel);
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (mWakeLock == null) {
+            PowerManager powerManager = getSystemService(PowerManager.class);
+            mWakeLock = powerManager.newWakeLock(PARTIAL_WAKE_LOCK, TAG);
+            mWakeLock.setReferenceCounted(false);
+        }
+        mWakeLock.acquire();
+
+        Notification notification = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("Inattentive Sleep CTS Tests")
+                .setContentText("Keeping partial wakelock during the test")
+                .setSmallIcon(android.R.drawable.ic_secure)
+                .build();
+
+        startForeground(1, notification);
+
+        return Service.START_NOT_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        if (mWakeLock != null) {
+            mWakeLock.release();
+        }
+    }
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
index 958db29..93d214f 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/AndroidManifest.xml
@@ -31,6 +31,8 @@
                 android:version="1"
                 android:certDigest="E4:95:82:FF:3A:0A:A4:C5:58:9F:C5:FE:AA:C6:B7:D6:E7:57:19:9D:D0:C6:74:2D:F7:BF:37:C2:FF:EF:95:F5">
         </uses-static-library>
+
+        <service android:name=".TestService" android:exported="true" />
     </application>
 
     <queries>
diff --git a/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java
new file mode 100644
index 0000000..b42c330
--- /dev/null
+++ b/hostsidetests/os/test-apps/StaticSharedLibConsumerApp1/src/android/os/lib/consumer1/TestService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.os.lib.consumer1;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class TestService extends Service {
+    private Binder mBinder = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
index 51589ea..0e5712c2a 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
@@ -21,6 +21,11 @@
     <application>
     </application>
 
+    <queries>
+        <package android:name="android.os.lib.provider"/>
+        <package android:name="android.os.lib.consumer1"/>
+    </queries>
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                      android:targetPackage="android.os.lib.app"/>
 </manifest>
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
index 29bb524..cdf03d6 100644
--- a/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/src/android/os/lib/app/StaticSharedLibsTests.java
@@ -21,10 +21,16 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.Manifest;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
+import android.os.IBinder;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.cts.install.lib.Install;
@@ -37,6 +43,8 @@
 import org.junit.runner.RunWith;
 
 import java.util.Optional;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 /**
  * On-device tests driven by StaticSharedLibsHostTests.
@@ -44,12 +52,32 @@
 @RunWith(AndroidJUnit4.class)
 public class StaticSharedLibsTests {
 
+    private static final String APK_BASE_PATH = "/data/local/tmp/cts/hostside/os/";
+    private static final String STATIC_LIB_PROVIDER1_APK = APK_BASE_PATH
+            + "CtsStaticSharedLibProviderApp1.apk";
+    private static final String STATIC_LIB_PROVIDER1_NAME = "foo.bar.lib";
+    private static final Long STATIC_LIB_PROVIDER1_VERSION = 1L;
+
+    private static final String STATIC_LIB_PROVIDER2_APK = APK_BASE_PATH
+            + "CtsStaticSharedLibProviderApp2.apk";
+    private static final String STATIC_LIB_PROVIDER2_PKG = "android.os.lib.provider";
+    private static final String STATIC_LIB_PROVIDER2_NAME = "foo.bar.lib";
+    private static final Long STATIC_LIB_PROVIDER2_VERSION = 2L;
+
     private static final String STATIC_LIB_PROVIDER5_PKG = "android.os.lib.provider";
     private static final String STATIC_LIB_PROVIDER5_NAME = "android.os.lib.provider_2";
+    private static final Long STATIC_LIB_PROVIDER5_VERSION = 1L;
     private static final TestApp TESTAPP_STATIC_LIB_PROVIDER5 = new TestApp(
             "TestStaticSharedLibProvider5", STATIC_LIB_PROVIDER5_PKG, 1, /*isApex*/ false,
             "CtsStaticSharedLibProviderApp5.apk");
 
+    public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
+
+    private static final ComponentName CONSUMERAPP1_TEST_SERVICE = ComponentName.createRelative(
+            "android.os.lib.consumer1", ".TestService");
+
+    private final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+
     @Before
     public void setUp() throws Exception {
         InstrumentationRegistry
@@ -57,12 +85,10 @@
                 .getUiAutomation()
                 .adoptShellPermissionIdentity(
                         Manifest.permission.INSTALL_PACKAGES);
-        clear();
     }
 
     @After
     public void tearDown() throws Exception {
-        clear();
         InstrumentationRegistry
                 .getInstrumentation()
                 .getUiAutomation()
@@ -71,31 +97,84 @@
 
     @Test
     public void testSamegradeStaticSharedLibFail() throws Exception {
-        Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
-        assertThat(getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME)).isNotNull();
+        try {
+            Install.single(TESTAPP_STATIC_LIB_PROVIDER5).commit();
+            assertThat(
+                    getSharedLibraryInfo(STATIC_LIB_PROVIDER5_NAME, STATIC_LIB_PROVIDER5_VERSION))
+                    .isNotNull();
 
-        InstallUtils.commitExpectingFailure(AssertionError.class,
-                "Packages declaring static-shared libs cannot be updated",
-                Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+            InstallUtils.commitExpectingFailure(AssertionError.class,
+                    "Packages declaring static-shared libs cannot be updated",
+                    Install.single(TESTAPP_STATIC_LIB_PROVIDER5));
+        } finally {
+            uninstallPackage(STATIC_LIB_PROVIDER5_PKG);
+        }
     }
 
-    private void clear() {
-        uninstallSharedLibrary(STATIC_LIB_PROVIDER5_PKG, STATIC_LIB_PROVIDER5_NAME);
+    @Test
+    public void testInstallStaticSharedLib_notKillDependentApp() throws Exception {
+        final Intent intent = new Intent();
+        intent.setComponent(CONSUMERAPP1_TEST_SERVICE);
+        final CountDownLatch kill = new CountDownLatch(1);
+        mServiceTestRule.bindService(intent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                kill.countDown();
+            }
+        }, Context.BIND_AUTO_CREATE);
+
+        try {
+            installPackage(STATIC_LIB_PROVIDER2_APK);
+            assertThat(
+                    getSharedLibraryInfo(STATIC_LIB_PROVIDER2_NAME, STATIC_LIB_PROVIDER2_VERSION))
+                    .isNotNull();
+
+            assertThat(kill.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isFalse();
+        } finally {
+            uninstallPackage(STATIC_LIB_PROVIDER2_PKG);
+        }
     }
 
-    private SharedLibraryInfo getSharedLibraryInfo(String libName) {
+    @Test
+    public void testSamegradeStaticSharedLib_killDependentApp() throws Exception {
+        final Intent intent = new Intent();
+        intent.setComponent(CONSUMERAPP1_TEST_SERVICE);
+        final CountDownLatch kill = new CountDownLatch(1);
+        mServiceTestRule.bindService(intent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                kill.countDown();
+            }
+        }, Context.BIND_AUTO_CREATE);
+        assertThat(
+                getSharedLibraryInfo(STATIC_LIB_PROVIDER1_NAME, STATIC_LIB_PROVIDER1_VERSION))
+                .isNotNull();
+
+        installPackage(STATIC_LIB_PROVIDER1_APK);
+        assertThat(kill.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+    }
+
+    private SharedLibraryInfo getSharedLibraryInfo(String libName, long version) {
         final PackageManager packageManager = InstrumentationRegistry.getContext()
                 .getPackageManager();
-        final Optional<SharedLibraryInfo> libraryInfo = packageManager.getSharedLibraries(0)
-                .stream().filter(lib -> lib.getName().equals(libName)).findAny();
+        final Optional<SharedLibraryInfo> libraryInfo =
+                packageManager.getSharedLibraries(0 /* flags */).stream().filter(
+                        lib -> lib.getName().equals(libName) && lib.getLongVersion() == version)
+                        .findFirst();
         return libraryInfo.isPresent() ? libraryInfo.get() : null;
     }
 
-    private void uninstallSharedLibrary(String packageName, String libName) {
-        if (getSharedLibraryInfo(libName) == null) {
-            return;
-        }
+    private boolean installPackage(String apkPath) {
+        return runShellCommand("pm install -t " + apkPath).equals("Success\n");
+    }
+
+    private void uninstallPackage(String packageName) {
         runShellCommand("pm uninstall " + packageName);
-        assertThat(getSharedLibraryInfo(libName)).isNull();
     }
 }
diff --git a/hostsidetests/os/test-apps/TEST_MAPPING b/hostsidetests/os/test-apps/TEST_MAPPING
new file mode 100644
index 0000000..8ee69ff
--- /dev/null
+++ b/hostsidetests/os/test-apps/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOsHostTestCases",
+      "file_patterns": [
+          "(/|^)StaticSharedLib.*",
+          "(/|^)StaticSharedNativeLib.*"
+      ],
+      "options": [
+        {
+          "include-filter": "android.os.cts.StaticSharedLibsHostTests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING b/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING
new file mode 100644
index 0000000..72ed050
--- /dev/null
+++ b/hostsidetests/os/test_mappings/packagemanager/TEST_MAPPING
@@ -0,0 +1,21 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsOsHostTestCases",
+      "options": [
+        {
+          "include-filter": "android.os.cts.StaticSharedLibsHostTests"
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        }
+      ]
+    }
+  ]
+}
diff --git a/hostsidetests/packagemanager/TEST_MAPPING b/hostsidetests/packagemanager/TEST_MAPPING
new file mode 100644
index 0000000..ab7465f
--- /dev/null
+++ b/hostsidetests/packagemanager/TEST_MAPPING
@@ -0,0 +1,33 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsCodePathHostTestCases"
+    },
+    {
+      "name": "CtsDomainVerificationHostTestCases"
+    },
+    {
+      "name": "CtsExtractNativeLibsHostTestCases"
+    },
+    {
+      "name": "CtsInstalledLoadingProgressHostTests"
+    },
+    {
+      "name": "CtsPackageManagerMultiUserHostTestCases"
+    },
+    {
+      "name": "CtsPackageManagerParsingHostTestCases"
+    },
+    {
+      "name": "CtsPackageManagerPreferredActivityHostTestCases"
+    },
+    {
+      "name": "CtsPackageManagerStatsHostTestCases"
+    }
+  ],
+  "presubmit-large": [
+    {
+      "name": "CtsDynamicMimeHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/packagemanager/codepath/Android.bp b/hostsidetests/packagemanager/codepath/Android.bp
deleted file mode 100644
index 1927ee3..0000000
--- a/hostsidetests/packagemanager/codepath/Android.bp
+++ /dev/null
@@ -1,33 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-java_test_host {
-    name: "CtsCodePathHostTestCases",
-    defaults: ["cts_defaults"],
-    srcs: ["src/**/*.java"],
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    libs: [
-        "cts-tradefed",
-        "tradefed",
-        "compatibility-host-util",
-    ],
-}
diff --git a/hostsidetests/packagemanager/codepath/AndroidTest.xml b/hostsidetests/packagemanager/codepath/AndroidTest.xml
deleted file mode 100644
index 083a00f..0000000
--- a/hostsidetests/packagemanager/codepath/AndroidTest.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<configuration description="Config for CTS package installation code path host test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
-    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
-        <option name="jar" value="CtsCodePathHostTestCases.jar" />
-    </test>
-</configuration>
-
diff --git a/hostsidetests/packagemanager/codepath/app/Android.bp b/hostsidetests/packagemanager/codepath/app/Android.bp
deleted file mode 100644
index 666244d..0000000
--- a/hostsidetests/packagemanager/codepath/app/Android.bp
+++ /dev/null
@@ -1,31 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "CodePathTestApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "current",
-    srcs: ["src/**/*.java",],
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    static_libs: ["androidx.test.rules"],
-    compile_multilib: "both",
-}
diff --git a/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml b/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml
deleted file mode 100644
index a9d5a03..0000000
--- a/hostsidetests/packagemanager/codepath/app/AndroidManifest.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.tests.codepath.app" >
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation
-        android:name="androidx.test.runner.AndroidJUnitRunner"
-        android:targetPackage="com.android.tests.codepath.app" />
-</manifest>
diff --git a/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java b/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java
deleted file mode 100644
index dd909c8..0000000
--- a/hostsidetests/packagemanager/codepath/app/src/com/android/tests/codepath/app/CodePathDeviceTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tests.codepath.app;
-
-import android.content.pm.PackageInfo;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class CodePathDeviceTest {
-    @Test
-    public void testCodePathMatchesExpected() throws Exception {
-        String expectedCodePath =
-                InstrumentationRegistry.getArguments().getString("expectedCodePath");
-        String packageName =
-                InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
-        PackageInfo pi = InstrumentationRegistry.getInstrumentation().getContext()
-                .getPackageManager().getPackageInfo(packageName, 0);
-        String apkPath = pi.applicationInfo.sourceDir;
-        Assert.assertTrue(apkPath.startsWith(expectedCodePath));
-    }
-}
diff --git a/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java b/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java
deleted file mode 100644
index 32763dd..0000000
--- a/hostsidetests/packagemanager/codepath/src/com/android/tests/codepath/host/CodePathTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tests.codepath.host;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.PackageInfo;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.HashMap;
-import java.util.Map;
-
-@RunWith(DeviceJUnit4ClassRunner.class)
-public class CodePathTest extends BaseHostJUnit4Test {
-    private static final String TEST_APK = "CodePathTestApp.apk";
-    private static final String TEST_PACKAGE = "com.android.tests.codepath.app";
-    private static final String TEST_CLASS = TEST_PACKAGE + "." + "CodePathDeviceTest";
-    private static final String TEST_METHOD = "testCodePathMatchesExpected";
-    private static final String CODE_PATH_ROOT = "/data/app";
-    private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
-
-    /** Uninstall apps after tests. */
-    @After
-    public void cleanUp() throws Exception {
-        uninstallPackage(getDevice(), TEST_PACKAGE);
-        Assert.assertFalse(isPackageInstalled(TEST_PACKAGE));
-    }
-
-    @Test
-    @AppModeFull
-    public void testAppInstallsWithReboot() throws Exception {
-        installPackage(TEST_APK);
-        Assert.assertTrue(isPackageInstalled(TEST_PACKAGE));
-        final String codePathFromDumpsys = getCodePathFromDumpsys(TEST_PACKAGE);
-        Assert.assertTrue(codePathFromDumpsys.startsWith(CODE_PATH_ROOT));
-        runDeviceTest(codePathFromDumpsys);
-        // Code paths should still be valid after reboot
-        getDevice().reboot();
-        final String codePathFromDumpsysAfterReboot = getCodePathFromDumpsys(TEST_PACKAGE);
-        Assert.assertEquals(codePathFromDumpsys, codePathFromDumpsysAfterReboot);
-        runDeviceTest(codePathFromDumpsys);
-    }
-
-    private String getCodePathFromDumpsys(String packageName)
-            throws DeviceNotAvailableException {
-        PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
-        return packageInfo.getCodePath();
-    }
-
-    private void runDeviceTest(String codePathToMatch) throws Exception {
-        final Map<String, String> testArgs = new HashMap<>();
-        testArgs.put("expectedCodePath", codePathToMatch);
-        runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS, TEST_METHOD,
-                null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
-                0L, true, false, testArgs);
-    }
-}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
index bf226ff..152a2b5 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileAllowParentLinkingTests.kt
@@ -18,6 +18,7 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4
 import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
 import com.android.bedstead.harrier.annotations.Postsubmit
 import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser
@@ -27,7 +28,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
 @RunWith(BedsteadJUnit4::class)
 class DomainVerificationWorkProfileAllowParentLinkingTests :
     DomainVerificationWorkProfileTestsBase() {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
index 4062d98..4586631 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileCrossProfileIntentTests.kt
@@ -21,6 +21,7 @@
 import android.content.IntentFilter
 import com.android.bedstead.harrier.BedsteadJUnit4
 import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
 import com.android.bedstead.harrier.annotations.Postsubmit
 import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser
@@ -31,7 +32,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
 @RunWith(BedsteadJUnit4::class)
 class DomainVerificationWorkProfileCrossProfileIntentTests :
     DomainVerificationWorkProfileTestsBase() {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
index dff94b8..21a300a 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkProfileTestsBase.kt
@@ -22,6 +22,7 @@
 import android.net.Uri
 import com.android.bedstead.harrier.BedsteadJUnit4
 import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
 import com.android.bedstead.harrier.annotations.AfterClass
 import com.android.bedstead.harrier.annotations.BeforeClass
 import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile
@@ -53,7 +54,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@EnsureHasWorkProfile(forUser = DeviceState.UserType.PRIMARY_USER)
+@EnsureHasWorkProfile(forUser = UserType.PRIMARY_USER)
 @RunWith(BedsteadJUnit4::class)
 abstract class DomainVerificationWorkProfileTestsBase {
 
@@ -101,7 +102,7 @@
         @BeforeClass
         fun installApks() {
             personalUser = deviceState.primaryUser()
-            workUser = deviceState.workProfile(DeviceState.UserType.PRIMARY_USER)
+            workUser = deviceState.workProfile(UserType.PRIMARY_USER)
             personalBrowsers = collectBrowsers(personalUser)
             workBrowsers = collectBrowsers(workUser)
             TestApis.packages().run {
diff --git a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
index 955a078..aba31b9 100644
--- a/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
+++ b/hostsidetests/packagemanager/domainverification/device/multiuser/src/com/android/cts/packagemanager/verify/domain/device/multiuser/DomainVerificationWorkUtils.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.UserManager
 import com.android.bedstead.harrier.DeviceState
+import com.android.bedstead.harrier.UserType
 import com.android.bedstead.nene.TestApis
 import com.android.bedstead.nene.users.UserReference
 import android.app.admin.RemoteDevicePolicyManager
@@ -37,7 +38,7 @@
 }
 
 internal fun DeviceState.getWorkDevicePolicyManager() =
-    profileOwner(workProfile(DeviceState.UserType.PRIMARY_USER))!!
+    profileOwner(workProfile(UserType.PRIMARY_USER))!!
             .devicePolicyManager()
 
 internal fun <T> withUserContext(user: UserReference, block: (context: Context) -> T) =
diff --git a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
index db9a827..c3423bc 100644
--- a/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
+++ b/hostsidetests/packagemanager/installedloadingprogess/hostside/src/com/android/tests/loadingprogress/host/IncrementalLoadingProgressTest.java
@@ -39,6 +39,8 @@
 
 import java.io.File;
 import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
@@ -125,6 +127,26 @@
 
     @LargeTest
     @Test
+    public void testGetLoadingProgressDuringMigration() throws Exception {
+        // Check partial loading progress
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testGetPartialLoadingProgress"));
+        final List<File> apks = new ArrayList<>(2);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File base_apk = buildHelper.getTestFile(TEST_APK);
+        assertNotNull(base_apk);
+        apks.add(base_apk);
+        final File split_apk = buildHelper.getTestFile(TEST_SPLIT_APK);
+        assertNotNull(split_apk);
+        apks.add(split_apk);
+        // Trigger app migration through normal package installation.
+        getDevice().installPackages(apks, false, "-t");
+        assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
+                "testGetFullLoadingProgress"));
+    }
+
+    @LargeTest
+    @Test
     public void testOnPackageLoadingProgressChangedCalledWithPartialLoaded() throws Exception {
         assertTrue(runDeviceTests(DEVICE_TEST_PACKAGE_NAME, TEST_CLASS_NAME,
                 "testOnPackageLoadingProgressChangedCalledWithPartialLoaded"));
diff --git a/hostsidetests/packagemanager/packagesetting/Android.bp b/hostsidetests/packagemanager/packagesetting/Android.bp
new file mode 100644
index 0000000..ba34621
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/Android.bp
@@ -0,0 +1,33 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsPackageSettingHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+}
diff --git a/hostsidetests/packagemanager/packagesetting/AndroidTest.xml b/hostsidetests/packagemanager/packagesetting/AndroidTest.xml
new file mode 100644
index 0000000..9218853
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Config for CTS package installation code path host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsPackageSettingHostTestCases.jar" />
+    </test>
+</configuration>
+
diff --git a/hostsidetests/packagemanager/packagesetting/app/Android.bp b/hostsidetests/packagemanager/packagesetting/app/Android.bp
new file mode 100644
index 0000000..2707570
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "PackageSettingTestApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    static_libs: ["androidx.test.rules"],
+    compile_multilib: "both",
+    platform_apis: true,
+}
diff --git a/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml b/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml
new file mode 100644
index 0000000..0072f74
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.tests.packagesetting.app" >
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.tests.packagesetting.app" />
+</manifest>
diff --git a/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java b/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java
new file mode 100644
index 0000000..ca3c4ed
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/app/src/com/android/tests/packagesetting/app/PackageSettingDeviceTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.packagesetting.app;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class PackageSettingDeviceTest {
+    @Test
+    public void testCodePathMatchesExpected() throws Exception {
+        String expectedCodePath =
+                InstrumentationRegistry.getArguments().getString("expectedCodePath");
+        String packageName =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+        PackageInfo pi = InstrumentationRegistry.getInstrumentation().getContext()
+                .getPackageManager().getPackageInfo(packageName, 0);
+        String apkPath = pi.applicationInfo.sourceDir;
+        Assert.assertTrue(apkPath.startsWith(expectedCodePath));
+    }
+
+    @Test
+    public void testFirstInstallTimeMatchesExpected() throws Exception {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        String packageName =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
+        String userIdStr = InstrumentationRegistry.getArguments().getString("userId");
+        String expectedFirstInstallTimeStr = InstrumentationRegistry.getArguments().getString(
+                "expectedFirstInstallTime");
+        final int userId = Integer.parseInt(userIdStr);
+        final long expectedFirstInstallTime = Long.parseLong(expectedFirstInstallTimeStr);
+        final Context contextAsUser = context.createContextAsUser(UserHandle.of(userId), 0);
+        PackageInfo pi = contextAsUser.getPackageManager().getPackageInfo(packageName,
+                PackageManager.PackageInfoFlags.of(0));
+        Assert.assertTrue(Math.abs(expectedFirstInstallTime - pi.firstInstallTime) < 1000 /* ms */);
+    }
+}
diff --git a/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java b/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java
new file mode 100644
index 0000000..0b66e17
--- /dev/null
+++ b/hostsidetests/packagemanager/packagesetting/src/com/android/tests/packagesetting/host/PackageSettingTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tests.packagesetting.host;
+
+import android.platform.test.annotations.AppModeFull;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.PackageInfo;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.TimeZone;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PackageSettingTest extends BaseHostJUnit4Test {
+    private static final String TEST_APK = "PackageSettingTestApp.apk";
+    private static final String TEST_PACKAGE = "com.android.tests.packagesetting.app";
+    private static final String TEST_CLASS = TEST_PACKAGE + "." + "PackageSettingDeviceTest";
+    private static final String CODE_PATH_ROOT = "/data/app";
+    private static final long DEFAULT_TIMEOUT = 10 * 60 * 1000L;
+    private int mSecondUser = -1;
+
+    /** Uninstall apps after tests. */
+    @After
+    public void cleanUp() throws Exception {
+        uninstallPackage(getDevice(), TEST_PACKAGE);
+        Assert.assertFalse(isPackageInstalled(TEST_PACKAGE));
+        if (mSecondUser != -1) {
+            stopAndRemoveUser(mSecondUser);
+        }
+    }
+
+    @Test
+    @AppModeFull
+    public void testAppInstallsWithReboot() throws Exception {
+        installPackage(TEST_APK);
+        Assert.assertTrue(isPackageInstalled(TEST_PACKAGE));
+        final String codePathFromDumpsys = getCodePathFromDumpsys(TEST_PACKAGE);
+        Assert.assertTrue(codePathFromDumpsys.startsWith(CODE_PATH_ROOT));
+        testCodePathMatchesDumpsys(codePathFromDumpsys);
+        // Code paths should still be valid after reboot
+        getDevice().reboot();
+        final String codePathFromDumpsysAfterReboot = getCodePathFromDumpsys(TEST_PACKAGE);
+        Assert.assertEquals(codePathFromDumpsys, codePathFromDumpsysAfterReboot);
+        testCodePathMatchesDumpsys(codePathFromDumpsys);
+    }
+
+    private String getCodePathFromDumpsys(String packageName)
+            throws DeviceNotAvailableException {
+        PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
+        return packageInfo.getCodePath();
+    }
+
+    private void testCodePathMatchesDumpsys(String codePathToMatch) throws Exception {
+        final Map<String, String> testArgs = new HashMap<>();
+        testArgs.put("expectedCodePath", codePathToMatch);
+        runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS, "testCodePathMatchesExpected",
+                null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT, 0L, true, false, testArgs);
+    }
+
+    @Test
+    @AppModeFull
+    public void testFirstInstallTimeWithReboot() throws Exception {
+        installPackage(TEST_APK);
+        final int currentUser = getDevice().getCurrentUser();
+        final String firstInstallTimeForCurrentUser = getFirstInstallTimeForUserFromDumpsys(
+                TEST_PACKAGE, currentUser);
+        Assert.assertNotNull(firstInstallTimeForCurrentUser);
+        testFirstInstallTimeMatchesDumpsys(firstInstallTimeForCurrentUser, currentUser);
+        // firstInstallTime should be the same after reboot
+        getDevice().reboot();
+        Assert.assertEquals(firstInstallTimeForCurrentUser,
+                getFirstInstallTimeForUserFromDumpsys(TEST_PACKAGE, currentUser));
+
+        mSecondUser = createAndStartSecondUser();
+        installPackageOnExistingUser(TEST_PACKAGE, mSecondUser);
+        final String firstInstallTimeForSecondUser = getFirstInstallTimeForUserFromDumpsys(
+                TEST_PACKAGE, mSecondUser);
+        Assert.assertNotNull(firstInstallTimeForSecondUser);
+        Assert.assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+        testFirstInstallTimeMatchesDumpsys(firstInstallTimeForSecondUser, mSecondUser);
+        getDevice().reboot();
+        Assert.assertEquals(firstInstallTimeForSecondUser,
+                getFirstInstallTimeForUserFromDumpsys(TEST_PACKAGE, mSecondUser));
+    }
+
+    private int createAndStartSecondUser() throws Exception {
+        String output = getDevice().executeShellCommand("pm create-user SecondUser");
+        Assert.assertTrue(output.startsWith("Success"));
+        int userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+        output = getDevice().executeShellCommand("am start-user -w " + userId);
+        Assert.assertFalse(output.startsWith("Error"));
+        output = getDevice().executeShellCommand("am get-started-user-state " + userId);
+        Assert.assertTrue(output.contains("RUNNING_UNLOCKED"));
+        return userId;
+    }
+
+    private void stopAndRemoveUser(int userId) throws Exception {
+        getDevice().executeShellCommand("am stop-user -w -f " + userId);
+        getDevice().executeShellCommand("pm remove-user " + userId);
+    }
+
+    private void installPackageOnExistingUser(String packageName, int userId) throws Exception {
+        final String output = getDevice().executeShellCommand(
+                String.format("pm install-existing --user %d %s", userId, packageName));
+        Assert.assertEquals("Package " + packageName + " installed for user: " + userId + "\n",
+                output);
+    }
+
+    private String getFirstInstallTimeForUserFromDumpsys(String packageName, int userId)
+            throws Exception {
+        PackageInfo packageInfo = getDevice().getAppPackageInfo(packageName);
+        return packageInfo.getFirstInstallTime(userId);
+    }
+
+    private void testFirstInstallTimeMatchesDumpsys(String firstInstallTime, int userId)
+            throws Exception {
+        final Map<String, String> testArgs = new HashMap<>();
+        // Notice the printed timestamp in dumpsys is formatted and has lost sub-second precision
+        final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+        sdf.setTimeZone(TimeZone.getTimeZone(getDeviceTimezone()));
+        final long firstInstallTs = sdf.parse(firstInstallTime).getTime();
+        testArgs.put("userId", String.valueOf(userId));
+        testArgs.put("expectedFirstInstallTime", String.valueOf(firstInstallTs));
+        runDeviceTests(getDevice(), null, TEST_PACKAGE, TEST_CLASS,
+                "testFirstInstallTimeMatchesExpected", null, DEFAULT_TIMEOUT, DEFAULT_TIMEOUT,
+                0L, true, false, testArgs);
+    }
+
+    private String getDeviceTimezone() throws Exception {
+        final String timezone = getDevice().getProperty("persist.sys.timezone");
+        if (timezone != null) {
+            return timezone.trim();
+        }
+        return "GMT";
+    }
+
+}
diff --git a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
index a34da7b..36d4bb0 100644
--- a/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
+++ b/hostsidetests/rollback/app/src/com/android/cts/rollback/host/app/HostTestHelper.java
@@ -671,6 +671,11 @@
         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
         assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
 
+        // This staged session should fail when there is already a staged rollback
+        InstallUtils.commitExpectingFailure(AssertionError.class,
+                "Session was failed by rollback",
+                Install.single(TestApp.C1).setStaged());
+
         // Assert that blocking staged session is failed
         final PackageInstaller.SessionInfo sessionA = InstallUtils.getStagedSessionInfo(sessionIdA);
         assertThat(sessionA).isNotNull();
diff --git a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
index f373d47..88e926a 100644
--- a/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
+++ b/hostsidetests/rollback/src/com/android/cts/rollback/host/RollbackManagerHostTest.java
@@ -81,6 +81,7 @@
                 + "--only-parent); do pm install-abandon $i; done");
         getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.A");
         getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.B");
+        getDevice().executeShellCommand("pm uninstall com.android.cts.install.lib.testapp.C");
         run("cleanUp");
         mHostUtils.uninstallShimApexIfNecessary();
     }
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index 61526d2..247992a 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -21,34 +21,49 @@
     manifest: "ScopedStorageTestHelper/TestAppA.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppB",
     manifest: "ScopedStorageTestHelper/TestAppB.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC",
     manifest: "ScopedStorageTestHelper/TestAppC.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC30",
     manifest: "ScopedStorageTestHelper/TestAppC30.xml",
@@ -58,8 +73,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
     manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
@@ -69,8 +89,13 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppDLegacy",
     manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
@@ -80,7 +105,11 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
 
 android_test_helper_app {
@@ -88,34 +117,49 @@
     manifest: "ScopedStorageTestHelper/TestAppFileManager.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppFileManagerBypassDB",
     manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
     manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
     static_libs: ["cts-scopedstorage-lib"],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
     manifest: "ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml",
@@ -125,7 +169,11 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
 
 android_test_helper_app {
@@ -150,52 +198,85 @@
     name: "ScopedStorageTest",
     manifest: "AndroidManifest.xml",
     srcs: ["src/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
     java_resources: [
         ":CtsScopedStorageTestAppA",
         ":CtsScopedStorageTestAppB",
         ":CtsScopedStorageTestAppC",
         ":CtsScopedStorageTestAppCLegacy",
-    ]
+    ],
 }
 
 android_test {
     name: "LegacyStorageTest",
     manifest: "legacy/AndroidManifest.xml",
     srcs: ["legacy/src/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
     target_sdk_version: "29",
     min_sdk_version: "30",
     java_resources: [
         ":CtsScopedStorageTestAppA",
-    ]
+    ],
 }
 
 java_test_host {
     name: "CtsScopedStorageCoreHostTest",
-    srcs:  [
+    srcs: [
         "host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java",
-        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
     ],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     test_config: "CoreTest.xml",
 }
 
 java_test_host {
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    static_libs: [
+        "modules-utils-build-testing",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     test_config: "AndroidTest.xml",
     data: [
         ":CtsLegacyStorageTestAppRequestLegacy",
@@ -205,13 +286,23 @@
 
 java_test_host {
     name: "GtsPreserveLegacyStorageHostTest",
-    srcs:  [
+    srcs: [
         "host/src/android/scopedstorage/cts/host/PreserveLegacyStorageHostTest.java",
-        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
     ],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
-    test_suites: ["general-tests", "gts"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    static_libs: [
+        "modules-utils-build-testing",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "general-tests",
+        "gts",
+    ],
     test_config: "AndroidPreserveLegacyTest.xml",
     data: [
         ":CtsLegacyStorageTestAppRequestLegacy",
@@ -222,21 +313,20 @@
 java_test_host {
     name: "CtsScopedStoragePublicVolumeHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    static_libs: ["modules-utils-build-testing", "compatibility-host-util"],
-    test_suites: ["general-tests", "mts-mediaprovider"],
-    test_config: "PublicVolumeTest.xml",
-}
-
-java_test_host {
-    name: "CtsAppCloningHostTest",
-    srcs:  [
-        "host/src/android/scopedstorage/cts/host/AppCloningHostTest.java",
-        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
     ],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
-    test_config: "AndroidTestAppCloning.xml",
+    static_libs: [
+        "modules-utils-build-testing",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+    ],
+    test_config: "PublicVolumeTest.xml",
 }
 
 android_test {
@@ -244,13 +334,24 @@
     manifest: "device/AndroidManifest.xml",
     test_config: "device/AndroidTest.xml",
     srcs: ["device/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
-    target_sdk_version: "31",
+    target_sdk_version: "current",
     min_sdk_version: "30",
-    libs: ["android.test.base", "android.test.mock", "android.test.runner",],
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
     java_resources: [
         ":CtsScopedStorageTestAppA",
         ":CtsScopedStorageTestAppB",
@@ -262,5 +363,5 @@
         ":CtsScopedStorageTestAppFileManagerBypassDB",
         ":CtsScopedStorageTestAppSystemGalleryBypassDB",
         ":CtsScopedStorageTestAppSystemGallery30BypassDB",
-    ]
+    ],
 }
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 42a3a36..320d541 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -28,6 +28,12 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
         <option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" />
     </target_preparer>
+
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml b/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
deleted file mode 100644
index 03802f2..0000000
--- a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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="Test for App cloning support with clone user profiles">
-    <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" />
-    <!-- TODO(b/169101565): change to secondary_user when fixed -->
-    <!-- Clone user profile is meant to exist only alongside a real system user.
-    It does not exist for a headless system user, or a secondary user -->
-    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class" value="android.scopedstorage.cts.host.AppCloningHostTest" />
-    </test>
-
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
-    </object>
-</configuration>
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index 5b725e1..325807d 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -27,6 +27,12 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
+
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
     </test>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index a93aeee..ee63a8a 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -24,6 +24,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
@@ -40,6 +41,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 
@@ -93,6 +95,7 @@
                 case CAN_READ_WRITE_QUERY:
                 case CREATE_FILE_QUERY:
                 case DELETE_FILE_QUERY:
+                case DELETE_RECURSIVE_QUERY:
                 case CAN_OPEN_FILE_FOR_READ_QUERY:
                 case CAN_OPEN_FILE_FOR_WRITE_QUERY:
                 case OPEN_FILE_FOR_READ_QUERY:
@@ -263,6 +266,9 @@
                 case DELETE_FILE_QUERY:
                     intent.putExtra(queryType, file.delete());
                     return intent;
+                case DELETE_RECURSIVE_QUERY:
+                    intent.putExtra(queryType, deleteRecursively(file));
+                    return intent;
                 case SETATTR_QUERY:
                     int newTimeMillis = 12345000;
                     intent.putExtra(queryType, file.setLastModified(newTimeMillis));
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index 7e6f895..5730b2e 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -23,6 +23,11 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
     </target_preparer>
 
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
index f0be82f..2e8ccda 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/RedactUriDeviceTest.java
@@ -58,7 +58,6 @@
 
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -272,7 +271,6 @@
      * redacted mode.
      **/
     @Test
-    @Ignore("Enable when b/194700183 is fixed")
     public void testSharedRedactedUri_openFileForRead() throws Exception {
         forceStopApp(APP_B_NO_PERMS.getPackageName());
         final File img = stageImageFileWithMetadata(IMAGE_FILE_NAME);
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 605f85c..756e425 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -29,8 +29,10 @@
 import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -43,6 +45,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
@@ -85,8 +88,10 @@
 import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
 import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
 import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
@@ -111,6 +116,7 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 
 import android.Manifest;
@@ -206,7 +212,7 @@
             "CtsScopedStorageTestAppFileManager.apk");
     // A legacy targeting app with RES and WES permissions
     private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
-            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
 
     // The following apps are not installed at test startup - please install before using.
     private static final TestApp APP_C = new TestApp("TestAppC",
@@ -520,7 +526,7 @@
     public void testCreateAndDeleteEmptyDir() throws Exception {
         final File externalFilesDir = getExternalFilesDir();
         // Remove directory in order to create it again
-        externalFilesDir.delete();
+        deleteRecursively(externalFilesDir);
 
         // Can create own external files dir
         assertThat(externalFilesDir.mkdir()).isTrue();
@@ -534,9 +540,9 @@
         assertThat(dir2.mkdir()).isTrue();
 
         // And can delete them all
-        assertThat(dir2.delete()).isTrue();
-        assertThat(dir1.delete()).isTrue();
-        assertThat(externalFilesDir.delete()).isTrue();
+        assertThat(deleteRecursively(dir2)).isTrue();
+        assertThat(deleteRecursively(dir1)).isTrue();
+        assertThat(deleteRecursively(externalFilesDir)).isTrue();
 
         // Can't create external dir for other apps
         final File nonexistentPackageFileDir = new File(
@@ -614,7 +620,7 @@
             // At this point, we're not sure who created this file, so we'll have both apps
             // deleting it
             mediaFile.delete();
-            dirInDownload.delete();
+            deleteRecursively(dirInDownload);
         }
     }
 
@@ -751,7 +757,7 @@
             assertThat(dir.list()).asList().doesNotContain(videoFileName);
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
-            dir.delete();
+            deleteRecursively(dir);
         }
     }
 
@@ -783,7 +789,7 @@
             assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
-            dir.delete();
+            deleteRecursively(dir);
         }
     }
 
@@ -1075,6 +1081,61 @@
         }
     }
 
+    void writeAndCheckMtime(final boolean append) throws Exception {
+        File file = new File(getDcimDir(), "update_modifies_mtime.jpg");
+
+        try {
+            assertThat(file.createNewFile()).isTrue();
+            assertThat(file.exists()).isTrue();
+
+            final long creationTime = file.lastModified();
+
+            // File should exist
+            assertNotEquals(creationTime, 0L);
+
+            // Sleep a bit more than 1 second because although
+            // File::lastModified() represents the duration in milliseconds,
+            // has 1 second precision.
+            // With lower sleep durations the test results flakey...
+            Thread.sleep(2000);
+
+            // Modification time should be the same as long the file has not
+            // been modified
+            assertEquals(creationTime, file.lastModified());
+
+            // Sleep a bit more than 1 second because although
+            // File::lastModified() represents the duration in milliseconds,
+            // has 1 second precision.
+            // With lower sleep durations the test results flakey...
+            Thread.sleep(2000);
+
+            // Assert we can write to the file
+            try (FileOutputStream fos = new FileOutputStream(file, append)) {
+                fos.write(BYTES_DATA1);
+                fos.close();
+            }
+
+            final long modificationTime = file.lastModified();
+
+            // As the file has been written, modification time should have
+            // changed
+            assertNotEquals(modificationTime, 0L);
+            assertNotEquals(modificationTime, creationTime);
+        } finally {
+            file.delete();
+        }
+    }
+
+    @Test
+    public void testAppendUpdatesMtime() throws Exception {
+        writeAndCheckMtime(true);
+    }
+
+    @Test
+    public void testWriteUpdatesMtime() throws Exception {
+        writeAndCheckMtime(false);
+    }
+
     @Test
     @SdkSuppress(minSdkVersion = 31, codeName = "S")
     public void testDefaultNoIsolatedStorageFlag() throws Exception {
@@ -1157,7 +1218,7 @@
         try {
             // Delete the directory if it already exists
             if (podcastsDir.exists()) {
-                deleteAsLegacyApp(podcastsDir);
+                deleteRecursivelyAsLegacyApp(podcastsDir);
             }
             assertThat(podcastsDir.exists()).isFalse();
             assertThat(podcastsDirLowerCase.exists()).isFalse();
@@ -1578,7 +1639,7 @@
             videoFile1.delete();
             videoFile2.delete();
             videoFile3.delete();
-            nonMediaDir.delete();
+            deleteRecursively(nonMediaDir);
         }
     }
 
@@ -1754,15 +1815,15 @@
 
         } finally {
             pdfFile.delete();
-            nonMediaDirectory.delete();
+            deleteRecursively(nonMediaDirectory);
 
             videoFile1.delete();
             videoFile2.delete();
             videoFile3.delete();
-            mediaDirectory1.delete();
-            mediaDirectory2.delete();
-            mediaDirectory3.delete();
-            mediaDirectory4.delete();
+            deleteRecursively(mediaDirectory1);
+            deleteRecursively(mediaDirectory2);
+            deleteRecursively(mediaDirectory3);
+            deleteRecursively(mediaDirectory4);
         }
     }
 
@@ -1788,7 +1849,8 @@
             assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
-            mediaDirectory1.delete();
+            deleteRecursively(mediaDirectory1);
+            deleteRecursively(mediaDirectory2);
         }
     }
 
@@ -1807,8 +1869,8 @@
             assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
             assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
         } finally {
-            emptyDirectoryOldPath.delete();
-            emptyDirectoryNewPath.delete();
+            deleteRecursively(emptyDirectoryOldPath);
+            deleteRecursively(emptyDirectoryNewPath);
         }
     }
 
@@ -1932,8 +1994,8 @@
         } finally {
             hiddenImageFile.delete();
             imageFile.delete();
-            hiddenDir.delete();
-            nonHiddenDir.delete();
+            deleteRecursively(hiddenDir);
+            deleteRecursively(nonHiddenDir);
         }
     }
 
@@ -1972,7 +2034,7 @@
             noMediaFile.delete();
             imageFile.delete();
             videoFile.delete();
-            directoryNoMedia.delete();
+            deleteRecursively(directoryNoMedia);
         }
     }
 
@@ -2057,7 +2119,7 @@
             // file.
             assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
 
-            trashFile(imageFileUri);
+            trashFileAndAssert(imageFileUri);
             // Check that only owner package, file manager and system gallery can list trashed image
             // file.
             assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
@@ -2066,7 +2128,7 @@
             // Check that only owner package, file manager can list pending non media file.
             assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
 
-            trashFile(pdfFileUri);
+            trashFileAndAssert(pdfFileUri);
             // Check that only owner package, file manager can list trashed non media file.
             assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
         } finally {
@@ -2198,6 +2260,65 @@
     }
 
     @Test
+    public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception {
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception {
+        final File otherImageFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+        final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+            final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+            assertNotNull(otherImageUri);
+
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+            // Test that we can move the file to "DCIM/"
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(updatedImageFileInDcim.exists()).isTrue();
+            assertThat(otherImageFile.exists()).isFalse();
+
+            values.clear();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + APP_B_NO_PERMS.getPackageName());
+            // Test that we can move the file back to other app's owned path
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(otherImageFile.exists()).isTrue();
+        } finally {
+            otherImageFile.delete();
+            updatedImageFileInDcim.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
     public void testQueryOtherAppsFiles() throws Exception {
         final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
@@ -2291,8 +2412,8 @@
             otherAppVideoFile2.delete();
             otherAppPdfFile1.delete();
             otherAppPdfFile2.delete();
-            dirInDcim.delete();
-            dirInPictures.delete();
+            deleteRecursively(dirInDcim);
+            deleteRecursively(dirInPictures);
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
@@ -2416,8 +2537,8 @@
             fileSpecialChars.delete();
             fileSpecialChars1.delete();
             fileSpecialChars2.delete();
-            dirSpecialChars.delete();
-            renamedDir.delete();
+            deleteRecursively(dirSpecialChars);
+            deleteRecursively(renamedDir);
         }
     }
 
@@ -2487,7 +2608,7 @@
         } finally {
             deleteAsLegacyApp(topLevelDir1);
             deleteAsLegacyApp(topLevelDir2);
-            nonTopLevelDir.delete();
+            deleteRecursively(nonTopLevelDir);
         }
     }
 
@@ -2725,18 +2846,57 @@
         }
     }
 
+    /**
+     * Tests that System Gallery apps cannot insert files in other app's private directories.
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Tests that System Gallery apps cannot update files in other app's private directories.
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testInsertFromExternalDirsViaRelativePath() throws Exception {
         verifyInsertFromExternalMediaDirViaRelativePath_allowed();
         verifyInsertFromExternalPrivateDirViaRelativePath_denied();
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testUpdateToExternalDirsViaRelativePath() throws Exception {
         verifyUpdateToExternalMediaDirViaRelativePath_allowed();
         verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
         int uid = Process.myUid();
@@ -2749,6 +2909,9 @@
         }
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
         int uid = Process.myUid();
@@ -2999,16 +3162,10 @@
         final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
         assertNotNull(trashedFileUri);
 
-        trashFile(trashedFileUri);
+        trashFileAndAssert(trashedFileUri);
         return trashedFileUri;
     }
 
-    private void trashFile(Uri uri) throws Exception {
-        final ContentValues values = new ContentValues();
-        values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
-        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
-    }
-
     /**
      * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
      * multiple db rows, file path is extracted from the first db row of the database query result.
@@ -3281,4 +3438,14 @@
         Log.d(TAG, "Deleting file " + file);
         deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
     }
+
+    /**
+     * Deletes the given file/directory recursively. If the file is a directory, then deletes all
+     * of its children (files or directories) recursively.
+     */
+    private void deleteRecursivelyAsLegacyApp(File dir) throws Exception {
+        // Use a legacy app to delete this directory, since it could be outside shared storage.
+        Log.d(TAG, "Deleting directory " + dir);
+        deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath());
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
deleted file mode 100644
index 281df8f..0000000
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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.scopedstorage.cts.host;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
-
-import android.platform.test.annotations.AppModeFull;
-
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.contentprovider.ContentProviderHandler;
-import com.android.tradefed.targetprep.TargetSetupError;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
-import com.android.tradefed.util.CommandResult;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Runs the AppCloning tests.
- */
-@RunWith(DeviceJUnit4ClassRunner.class)
-@AppModeFull
-public class AppCloningHostTest extends BaseHostTestCase {
-    private static final String APP_A = "CtsScopedStorageTestAppA.apk";
-    private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres";
-    private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
-    private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
-    private String mCloneUserId;
-    private ContentProviderHandler mContentProviderHandler;
-
-
-    @Before
-    public void setup() throws Exception {
-        assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
-        assumeTrue(isAtLeastS());
-
-        String output = executeShellCommand(
-                "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
-                        + "testUser");
-        mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", "");
-        assertThat(mCloneUserId).isNotEmpty();
-        CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
-        assertThat(out.getStderr()).isEmpty();
-        mContentProviderHandler = new ContentProviderHandler(getDevice());
-        mContentProviderHandler.setUp();
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (isHeadlessSystemUserMode() || !isAtLeastS()) return;
-        mContentProviderHandler.tearDown();
-        executeShellCommand("pm remove-user %s", mCloneUserId);
-    }
-
-    @Test
-    public void testInstallAppTwice() throws Exception {
-        installAppAsUser(APP_A, getCurrentUserId());
-        installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
-        uninstallPackage(APP_A_PACKAGE);
-    }
-
-    @Test
-    public void testCreateCloneUserFile() throws Exception {
-        CommandResult out;
-
-        // Check that the clone user directories exist
-        eventually(() -> {
-            // Wait for finish.
-            assertThat(isSuccessful(
-                    runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue();
-        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
-
-        // Create a file on the clone user storage
-        out = executeShellV2Command("touch /sdcard/testFile.txt");
-        assertThat(isSuccessful(out)).isTrue();
-        eventually(() -> {
-            // Wait for finish.
-            assertThat(isSuccessful(
-                    runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt",
-                            "< /sdcard/testFile.txt"))).isTrue();
-        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
-
-        // Check that the above created file exists on the clone user storage
-        out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", "");
-        assertThat(isSuccessful(out)).isTrue();
-
-        // Cleanup the created file
-        out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", "");
-        assertThat(isSuccessful(out)).isTrue();
-    }
-
-    @Test
-    public void testPrivateAppDataDirectoryForCloneUser() throws Exception {
-        installAppAsUser(APP_A, Integer.valueOf(mCloneUserId));
-        eventually(() -> {
-            // Wait for finish.
-            assertThat(isPackageInstalled(APP_A_PACKAGE, mCloneUserId)).isTrue();
-        }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
-    }
-
-    private void installAppAsUser(String packageFile, int userId)
-            throws TargetSetupError, DeviceNotAvailableException {
-        installPackageAsUser(packageFile, false, userId, "-t");
-    }
-
-    private CommandResult runContentProviderCommand(String commandType, String userId,
-            String relativePath, String args) throws Exception {
-        String fullUri = CONTENT_PROVIDER_URL + relativePath;
-        return executeShellV2Command("content %s --user %s --uri %s %s",
-                commandType, userId, fullUri, args);
-    }
-
-}
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 3b81646..5638e41 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -111,6 +111,46 @@
     }
 
     @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW");
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW");
+    }
+
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery");
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery");
+    }
+
+    @Test
     public void testMkdirInRandomPlaces_hasW() throws Exception {
         revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
         executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770");
@@ -147,6 +187,11 @@
     }
 
     @Test
+    public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+        runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW");
+    }
+
+    @Test
     public void testCantRename_hasR() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
         runDeviceTest("testCantRename_hasR");
@@ -214,6 +259,15 @@
         runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
     }
 
+    /**
+     * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+     * non default top level directory.
+     */
+    @Test
+    public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+        runDeviceTest("testLegacySystemGalleryCanUpdateToExistingDirectory");
+    }
+
     @Test
     public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -249,4 +303,18 @@
     public void testUpdateToExternalDirsViaRelativePath() throws Exception {
         runDeviceTest("testUpdateToExternalDirsViaRelativePath");
     }
+
+    private void allowAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+                    + op + " allow");
+        }
+    }
+
+    private void denyAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+                    + op + " deny");
+        }
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index f1236b6..cd9378d 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -125,6 +125,26 @@
     }
 
     @Test
+    public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testCheckInstallerAppAccessToObbDirs() throws Exception {
         allowAppOps("android:request_install_packages");
         grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -179,6 +199,26 @@
     }
 
     @Test
+    public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testOpenOtherPendingFilesFromFuse() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index c602f0a..c85b090 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application  android:requestLegacyExternalStorage="true" >
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4754d74..40c9063 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -23,7 +23,9 @@
 import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
@@ -31,6 +33,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
@@ -38,6 +41,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
 import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
 import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -46,9 +50,13 @@
 import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.updateFile;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
@@ -59,6 +67,7 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 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.assertNotEquals;
@@ -126,12 +135,14 @@
      * test runs.
      */
     static final String NONCE = String.valueOf(System.nanoTime());
-    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+    static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
 
     static final String IMAGE_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".jpg";
     static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
 
+    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+
     // The following apps are installed before the tests are run via a target_preparer.
     // See test config for details.
     // An app with READ_EXTERNAL_STORAGE permission
@@ -341,7 +352,7 @@
         try {
             assertThat(newDir.mkdir()).isFalse();
         } finally {
-            newDir.delete();
+            deleteRecursively(newDir);
         }
     }
 
@@ -426,8 +437,25 @@
 
             pdfFile1.delete();
             pdfFile2.delete();
-            nonMediaDir1.delete();
-            nonMediaDir2.delete();
+            deleteRecursively(nonMediaDir1);
+            deleteRecursively(nonMediaDir2);
+        }
+    }
+
+    @Test
+    public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
         }
     }
 
@@ -521,8 +549,8 @@
             // UNIQUE constraint error.
             TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath);
         } finally {
-            directoryOldPath.delete();
-            directoryNewPath.delete();
+            deleteRecursively(directoryOldPath);
+            deleteRecursively(directoryNewPath);
         }
     }
 
@@ -698,7 +726,7 @@
             imageInNoMediaDir.delete();
             renamedImageInDCIM.delete();
             noMediaFile.delete();
-            directoryNoMedia.delete();
+            deleteRecursively(directoryNoMedia);
         }
     }
 
@@ -852,6 +880,41 @@
         }
     }
 
+    /**
+     * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+     * non default top level directory.
+     */
+    @Test
+    public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        // Top level non default directory
+        final File topLevelTestDirectory = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+        final File imageFileInTopLevelDir = new File(topLevelTestDirectory, IMAGE_FILE_NAME);
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final Uri imageUri = MediaStore.scanFile(getContentResolver(), imageFile);
+            assertThat(imageUri).isNotNull();
+
+            topLevelTestDirectory.mkdirs();
+            assertThat(topLevelTestDirectory.exists()).isTrue();
+
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, topLevelTestDirectory.getName());
+            final int result = getContentResolver().update(imageUri, values, Bundle.EMPTY);
+            assertWithMessage("Result of update() from DCIM -> top level test directory")
+                    .that(result).isEqualTo(1);
+            assertThat(imageFileInTopLevelDir.exists()).isTrue();
+        } finally {
+            imageFile.delete();
+            imageFileInTopLevelDir.delete();
+            deleteRecursively(topLevelTestDirectory);
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
     @Test
     public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
@@ -939,6 +1002,82 @@
     }
 
     /**
+     * Tests that legacy apps cannot insert in other app private directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps cannot update in other app private directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+        TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private
+     * directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private
+     * directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy System Gallery apps cannot insert in other app private directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Tests that legacy System Gallery apps cannot update in other app private directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
      * Make sure inserting files from app private directories in legacy apps is allowed via DATA.
      */
     @Test
@@ -947,7 +1086,7 @@
 
         ContentValues values = new ContentValues();
         final String androidObbDir =
-                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+                TestUtils.getExternalObbDir().toString() + "/" + System.currentTimeMillis();
         values.put(MediaStore.MediaColumns.DATA, androidObbDir);
         insertFile(values);
 
@@ -981,7 +1120,7 @@
         assertNotEquals(0, updateFile(uri, values));
 
         final String androidObbDir =
-                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+                TestUtils.getExternalObbDir().toString() + "/" + System.currentTimeMillis();
         values.put(MediaStore.MediaColumns.DATA, androidObbDir);
         assertNotEquals(0, updateFile(uri, values));
 
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 041e5ce..314dbb4 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -16,6 +16,7 @@
 
 package android.scopedstorage.cts.lib;
 
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
 
 import static androidx.test.InstrumentationRegistry.getContext;
@@ -98,6 +99,7 @@
     public static final String CREATE_IMAGE_ENTRY_QUERY =
             "android.scopedstorage.cts.createimageentry";
     public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
+    public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive";
     public static final String CAN_OPEN_FILE_FOR_READ_QUERY =
             "android.scopedstorage.cts.can_openfile_read";
     public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY =
@@ -295,6 +297,17 @@
     }
 
     /**
+     * Makes the given {@code testApp} delete a file or directory.
+     * If the file is a directory, then deletes all of its children (file or directories)
+     * recursively.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception {
+        return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY);
+    }
+
+    /**
      * Makes the given {@code testApp} delete a file. Doesn't throw in case of failure.
      */
     public static boolean deleteFileAsNoThrow(TestApp testApp, String path) {
@@ -417,10 +430,8 @@
     }
 
     public static void verifyInsertFromExternalPrivateDirViaRelativePath_denied() throws Exception {
-        resetDefaultExternalStorageVolume();
-
         // Test that inserting files from Android/obb/.. is not allowed.
-        final String androidObbDir = getContext().getObbDir().toString();
+        final String androidObbDir = getExternalObbDir().toString();
         ContentValues values = new ContentValues();
         values.put(
                 MediaStore.MediaColumns.RELATIVE_PATH,
@@ -436,8 +447,6 @@
     }
 
     public static void verifyInsertFromExternalMediaDirViaRelativePath_allowed() throws Exception {
-        resetDefaultExternalStorageVolume();
-
         // Test that inserting files from Android/media/.. is allowed.
         final String androidMediaDir = getExternalMediaDir().toString();
         final ContentValues values = new ContentValues();
@@ -448,13 +457,11 @@
     }
 
     public static void verifyInsertFromExternalPrivateDirViaData_denied() throws Exception {
-        resetDefaultExternalStorageVolume();
-
         ContentValues values = new ContentValues();
 
         // Test that inserting files from Android/obb/.. is not allowed.
         final String androidObbDir =
-                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+                getExternalObbDir().toString() + "/" + System.currentTimeMillis();
         values.put(MediaStore.MediaColumns.DATA, androidObbDir);
         assertThrows(IllegalArgumentException.class, () -> insertFile(values));
 
@@ -465,8 +472,6 @@
     }
 
     public static void verifyInsertFromExternalMediaDirViaData_allowed() throws Exception {
-        resetDefaultExternalStorageVolume();
-
         // Test that inserting files from Android/media/.. is allowed.
         ContentValues values = new ContentValues();
         final String androidMediaDirFile =
@@ -477,7 +482,6 @@
 
     // NOTE: While updating, DATA field should be ignored for all the apps including file manager.
     public static void verifyUpdateToExternalDirsViaData_denied() throws Exception {
-        resetDefaultExternalStorageVolume();
         Uri uri = insertFileFromExternalMedia(false);
 
         final String androidMediaDirFile =
@@ -487,7 +491,7 @@
         assertEquals(0, updateFile(uri, values));
 
         final String androidObbDir =
-                getContext().getObbDir().toString() + "/" + System.currentTimeMillis();
+                getExternalObbDir().toString() + "/" + System.currentTimeMillis();
         values.put(MediaStore.MediaColumns.DATA, androidObbDir);
         assertEquals(0, updateFile(uri, values));
 
@@ -498,7 +502,6 @@
 
     public static void verifyUpdateToExternalMediaDirViaRelativePath_allowed()
             throws IOException {
-        resetDefaultExternalStorageVolume();
         Uri uri = insertFileFromExternalMedia(true);
 
         // Test that update to files from Android/media/.. is allowed.
@@ -512,11 +515,10 @@
 
     public static void verifyUpdateToExternalPrivateDirsViaRelativePath_denied()
             throws Exception {
-        resetDefaultExternalStorageVolume();
         Uri uri = insertFileFromExternalMedia(true);
 
         // Test that update to files from Android/obb/.. is not allowed.
-        final String androidObbDir = getContext().getObbDir().toString();
+        final String androidObbDir = getExternalObbDir().toString();
         ContentValues values = new ContentValues();
         values.put(
                 MediaStore.MediaColumns.RELATIVE_PATH,
@@ -950,6 +952,111 @@
     }
 
     /**
+     * Assert that app cannot insert files in other app's private directories
+     *
+     * @param fileName name of the file
+     * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not
+     * respected, will not throw an Exception as the Data value is ignored.
+     * @param otherApp Other test app in whose external private directory we will attempt to insert
+     * @param callingPackageName Calling package name
+     */
+    public static void assertCantInsertToOtherPrivateAppDirectories(String fileName,
+            boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+            throws Exception {
+        // Create directory in which the device test will try to insert file to
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                callingPackageName, otherApp.getPackageName()));
+        final File file = new File(otherAppExternalDataDir, fileName);
+        try {
+            assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+
+            final ContentValues valuesWithData = new ContentValues();
+            valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+            try {
+                Uri uri = getContentResolver().insert(
+                        MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithData);
+
+                if (throwsExceptionForDataValue) {
+                    fail("File insert expected to fail: " + file);
+                } else {
+                    try (Cursor c = getContentResolver().query(uri, new String[]{
+                            MediaStore.MediaColumns.DATA}, null, null)) {
+                        assertThat(c.moveToFirst()).isTrue();
+                        assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath());
+                    }
+                }
+            } catch (IllegalArgumentException expected) {
+            }
+
+            final ContentValues valuesWithRelativePath = new ContentValues();
+            final String path = file.getAbsolutePath();
+            valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    path.substring(path.indexOf("Android")));
+            valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+            try {
+                getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithRelativePath);
+                fail("File insert expected to fail: " + file);
+            } catch (IllegalArgumentException expected) {
+            }
+        } finally {
+            deleteFileAsNoThrow(otherApp, file.getPath());
+        }
+    }
+
+    /**
+     * Assert that app cannot update files in other app's private directories
+     *
+     * @param fileName name of the file
+     * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which
+     * Data column is not respected, will not throw an Exception as the Data value is ignored.
+     * @param otherApp Other test app in whose external private directory we will attempt to insert
+     * @param callingPackageName Calling package name
+     */
+    public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName,
+            boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+            throws Exception {
+        // Create priv-app file and add to the database that we will try to update
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                callingPackageName, otherApp.getPackageName()));
+        final File file = new File(otherAppExternalDataDir, fileName);
+        try {
+            assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+            MediaStore.scanFile(getContentResolver(), file);
+
+            final ContentValues valuesWithData = new ContentValues();
+            valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+            try {
+                int res = getContentResolver().update(
+                        MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithData, Bundle.EMPTY);
+
+                if (throwsExceptionForDataValue) {
+                    fail("File update expected to fail: " + file);
+                } else {
+                    assertThat(res).isEqualTo(0);
+                }
+            } catch (IllegalArgumentException expected) {
+            }
+
+            final ContentValues valuesWithRelativePath = new ContentValues();
+            final String path = file.getAbsolutePath();
+            valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    path.substring(path.indexOf("Android")));
+            valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+            try {
+                getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithRelativePath, Bundle.EMPTY);
+                fail("File update expected to fail: " + file);
+            } catch (IllegalArgumentException expected) {
+            }
+        } finally {
+            deleteFileAsNoThrow(otherApp, file.getPath());
+        }
+    }
+
+    /**
      * Asserts can rename directory.
      */
     public static void assertCanRenameDirectory(File oldDirectory, File newDirectory,
@@ -1193,6 +1300,18 @@
     }
 
     /**
+     * Creates and returns the Android obb sub-directory belonging to the calling package.
+     */
+    public static File getExternalObbDir() {
+        final String packageName = getContext().getPackageName();
+        final File res = new File(getAndroidObbDir(), packageName);
+        if (!res.equals(getContext().getObbDirs()[0])) {
+            res.mkdirs();
+        }
+        return res;
+    }
+
+    /**
      * Creates and returns the Android media sub-directory belonging to the calling package.
      */
     public static File getExternalMediaDir() {
@@ -1272,6 +1391,10 @@
         return new File(getAndroidDir(), "data");
     }
 
+    public static File getAndroidObbDir() {
+        return new File(getAndroidDir(), "obb");
+    }
+
     public static File getAndroidMediaDir() {
         return new File(getAndroidDir(), "media");
     }
@@ -1355,7 +1478,8 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(actionName);
         intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
-        getContext().registerReceiver(broadcastReceiver, intentFilter);
+        getContext().registerReceiver(broadcastReceiver, intentFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         // Launch the test app.
         intent.setPackage(testApp.getPackageName());
@@ -1738,4 +1862,21 @@
                 ActivityManager.class).getRunningAppProcesses().stream().filter(
                         p -> packageName.equals(p.processName)).findFirst();
     }
+
+    public static void trashFileAndAssert(Uri uri) {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+        assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash "
+                 + "file " + values)
+                .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+    }
+
+    public static void untrashFileAndAssert(Uri uri) {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_TRASHED, 0);
+        assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash "
+                + "file " + values)
+                .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+    }
+
 }
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index c915235..ebc8f10 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -21,6 +21,8 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -30,6 +32,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
 import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -52,6 +55,8 @@
 import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
@@ -66,6 +71,7 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -76,8 +82,10 @@
 
 import android.Manifest;
 import android.app.WallpaperManager;
+import android.content.ContentValues;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
@@ -122,6 +130,7 @@
 
     static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
     static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
 
     // The following apps are installed before the tests are run via a target_preparer.
@@ -136,7 +145,7 @@
             "CtsScopedStorageTestAppB.apk");
     // A legacy targeting app with RES and WES permissions
     private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
-            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
 
     @Before
     public void setup() throws Exception {
@@ -222,6 +231,28 @@
                 });
     }
 
+    /**
+     * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's
+     * private directories.
+     */
+    @Test
+    public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's
+     * private directories.
+     */
+    @Test
+    public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
     @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
         pollForManageExternalStorageAllowed();
@@ -299,7 +330,8 @@
             final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
                     THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
             final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
-            final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
+            final File otherAppExternalDataFile =
+                    new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME);
             assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
                     .isTrue();
 
@@ -489,7 +521,7 @@
             nomediaFile.delete();
             mediaFile.delete();
             renamedMediaFile.delete();
-            nomediaDir.delete();
+            deleteRecursively(nomediaDir);
         }
     }
 
@@ -526,14 +558,70 @@
             mediaFile1InSubDir.delete();
             mediaFile2InSubDir.delete();
             topLevelNomediaFile.delete();
-            nomediaSubDir.delete();
-            nomediaDir.delete();
+            deleteRecursively(nomediaSubDir);
+            deleteRecursively(nomediaDir);
             // Scan the directory to remove stale db rows.
             MediaStore.scanFile(getContentResolver(), nomediaDir);
         }
     }
 
     @Test
+    public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
+        }
+    }
+
+    @Test
+    public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File otherImageFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+        final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+            final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+            assertNotNull(otherImageUri);
+
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+            // Test that we can move the file to "DCIM/"
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(updatedImageFileInDcim.exists()).isTrue();
+            assertThat(otherImageFile.exists()).isFalse();
+
+            values.clear();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + APP_B_NO_PERMS.getPackageName());
+            // Test that we can move the file back to other app's owned path
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+        } finally {
+            otherImageFile.delete();
+            updatedImageFileInDcim.delete();
+        }
+    }
+
+    @Test
     public void testAndroidMedia() throws Exception {
         // Check that the app does not have legacy external storage access
         if (isAtLeastS()) {
@@ -818,8 +906,8 @@
             imageFile.delete();
             renamedImageFile.delete();
             imageFileInRenamedDir.delete();
-            dir.delete();
-            renamedDir.delete();
+            deleteRecursively(dir);
+            deleteRecursively(renamedDir);
         }
     }
 
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
index 4bf8be0..ad9b58d 100755
--- a/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-115739809/poc.cpp
@@ -79,6 +79,8 @@
         case InputMessage::Type::MOTION: {
             // int32_t eventId
             outMsg->body.motion.eventId = msg.body.key.eventId;
+            // uint32_t pointerCount
+            outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
             // nsecs_t eventTime
             outMsg->body.motion.eventTime = msg.body.motion.eventTime;
             // int32_t deviceId
@@ -125,14 +127,18 @@
             outMsg->body.motion.xCursorPosition = msg.body.motion.xCursorPosition;
             // float yCursorPosition
             outMsg->body.motion.yCursorPosition = msg.body.motion.yCursorPosition;
-            // uint32_t displayOrientation
-            outMsg->body.motion.displayOrientation = msg.body.motion.displayOrientation;
-            // int32_t displayW
-            outMsg->body.motion.displayWidth = msg.body.motion.displayWidth;
-            // int32_t displayH
-            outMsg->body.motion.displayHeight = msg.body.motion.displayHeight;
-            // uint32_t pointerCount
-            outMsg->body.motion.pointerCount = msg.body.motion.pointerCount;
+            // float dsdxDisplay
+            outMsg->body.motion.dsdxRaw = msg.body.motion.dsdxRaw;
+            // float dtdxDisplay
+            outMsg->body.motion.dtdxRaw = msg.body.motion.dtdxRaw;
+            // float dtdyDisplay
+            outMsg->body.motion.dtdyRaw = msg.body.motion.dtdyRaw;
+            // float dsdyDisplay
+            outMsg->body.motion.dsdyRaw = msg.body.motion.dsdyRaw;
+            // float txDisplay
+            outMsg->body.motion.txRaw = msg.body.motion.txRaw;
+            // float tyDisplay
+            outMsg->body.motion.tyRaw = msg.body.motion.tyRaw;
             //struct Pointer pointers[MAX_POINTERS]
             for (size_t i = 0; i < msg.body.motion.pointerCount; i++) {
                 // PointerProperties properties
@@ -158,7 +164,6 @@
         case InputMessage::Type::FOCUS: {
             outMsg->body.focus.eventId = msg.body.focus.eventId;
             outMsg->body.focus.hasFocus = msg.body.focus.hasFocus;
-            outMsg->body.focus.inTouchMode = msg.body.focus.inTouchMode;
             break;
         }
         case InputMessage::Type::CAPTURE: {
@@ -178,6 +183,10 @@
             outMsg->body.timeline.graphicsTimeline = msg.body.timeline.graphicsTimeline;
             break;
         }
+        case InputMessage::Type::TOUCH_MODE: {
+            outMsg->body.touchMode.eventId = msg.body.timeline.eventId;
+            outMsg->body.touchMode.isInTouchMode = msg.body.touchMode.isInTouchMode;
+        }
     }
 }
 
@@ -251,9 +260,10 @@
     }
 
     InputMessage::Type types[] = {
-            InputMessage::Type::KEY,      InputMessage::Type::MOTION,  InputMessage::Type::FINISHED,
-            InputMessage::Type::FOCUS,    InputMessage::Type::CAPTURE, InputMessage::Type::DRAG,
-            InputMessage::Type::TIMELINE,
+            InputMessage::Type::KEY,      InputMessage::Type::MOTION,
+            InputMessage::Type::FINISHED, InputMessage::Type::FOCUS,
+            InputMessage::Type::CAPTURE,  InputMessage::Type::DRAG,
+            InputMessage::Type::TIMELINE, InputMessage::Type::TOUCH_MODE,
     };
     for (InputMessage::Type type : types) {
         bool success = checkMessage(*server, *client, type);
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
index e959c3a..a2f7e2f 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2017-0479/Android.bp
@@ -21,6 +21,7 @@
     defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: ["poc.cpp"],
     shared_libs: [
+        "android.media.audio.common.types-V1-cpp",
         "audioclient-types-aidl-cpp",
         "audioflinger-aidl-cpp",
         "libmedia",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
index 3638d08..0f49af7 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/Android.bp
@@ -30,8 +30,8 @@
         "-DCHECK_OVERFLOW",
     ],
     compile_multilib: "32",
-    include_dirs: [
-        "frameworks/av/media/libstagefright/rtsp/",
+    header_libs: [
+        "libstagefright_rtsp_headers",
     ],
     shared_libs: [
         "libmediaplayerservice",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
index e733c6f..ad4c8b8 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2019-2044/poc.cpp
@@ -13,9 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <media/stagefright/rtsp/APacketSource.h>
+#include <media/stagefright/rtsp/ASessionDescription.h>
 #include <stdlib.h>
-#include <APacketSource.h>
-#include <ASessionDescription.h>
 
 using namespace android;
 
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
index 807b9106..877a566 100644
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2020-0073/Android.bp
@@ -14,6 +14,7 @@
  * limitations under the License.
  *
  */
+
 package {
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
index 810f92d..c9c1d61 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2018_9558.java
@@ -17,9 +17,10 @@
 package android.security.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
+
 import com.android.compatibility.common.util.CrashUtils;
-import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -37,10 +38,10 @@
         assumeIsSupportedNfcDevice(getDevice());
         pocPusher.only64();
         String binaryName = "CVE-2018-9558";
-        String signals[] = {CrashUtils.SIGABRT};
+        String[] signals = {CrashUtils.SIGABRT};
         AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
         testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
-        testConfig.config.setSignals(signals);
+        testConfig.config.setSignals(CrashUtils.SIGABRT);
         AdbUtils.runPocAssertNoCrashesNotVulnerable(testConfig);
     }
 }
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
index 2c39674..64fed4f 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2020_0073.java
@@ -16,13 +16,13 @@
 
 package android.security.cts;
 
-import com.android.tradefed.device.ITestDevice;
-import com.android.compatibility.common.util.CrashUtils;
-
 import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.compatibility.common.util.CrashUtils;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CVE_2020_0073 extends SecurityTestCase {
@@ -38,7 +38,7 @@
         assumeIsSupportedNfcDevice(getDevice());
         pocPusher.only64();
         String binaryName = "CVE-2020-0073";
-        String signals[] = {CrashUtils.SIGABRT};
+        String[] signals = {CrashUtils.SIGABRT};
         AdbUtils.pocConfig testConfig = new AdbUtils.pocConfig(binaryName, getDevice());
         testConfig.config = new CrashUtils.Config().setProcessPatterns(binaryName);
         testConfig.config.setSignals(signals);
diff --git a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
index e4878a0..3e613aa 100644
--- a/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
+++ b/hostsidetests/securitybulletin/src/android/security/cts/CVE_2021_0430.java
@@ -17,9 +17,11 @@
 package android.security.cts;
 
 import android.platform.test.annotations.AsbSecurityTest;
+
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class CVE_2021_0430 extends SecurityTestCase {
diff --git a/hostsidetests/settings/Android.bp b/hostsidetests/settings/Android.bp
index 66a198b..0b5bd4a 100644
--- a/hostsidetests/settings/Android.bp
+++ b/hostsidetests/settings/Android.bp
@@ -30,7 +30,6 @@
     ],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
index ec63294..7e1cd74 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -42,7 +42,6 @@
     ],
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
     ],
diff --git a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
index 19f2b58..595e1ad 100644
--- a/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
+++ b/hostsidetests/shortcuts/deviceside/backup/publisher4old/src/android/content/pm/cts/shortcut/backup/publisher4/ShortcutManagerPostBackupTest.java
@@ -316,7 +316,8 @@
                 latch.countDown();
             }
         };
-        getContext().registerReceiver(onResult, myFilter);
+        getContext().registerReceiver(onResult, myFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
         assertTrue(getManager().requestPinShortcut(ms2,
                 PendingIntent.getBroadcast(getContext(), 0, new Intent(myIntentAction),
                         PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE_UNAUDITED).getIntentSender()));
diff --git a/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java b/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
index fb8ad92..35c5a72 100644
--- a/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
+++ b/hostsidetests/silentupdate/src/com/android/tests/hostside/silentupdate/SilentUpdateHostsideTests.java
@@ -135,6 +135,25 @@
                 "silentInstallRepeatedly_waitForThrottleTime_succeed");
     }
 
+    @Test
+    public void newInstall_withInstallDpcPermission_requiresUserAction() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_CLS, "newInstall_withInstallDpcPermission_requiresUserAction");
+    }
+
+    @Test
+    public void newInstallDpc_withInstallDpcPermission_requiresNoUserAction() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_CLS, "newInstallDpc_withInstallDpcPermission_requiresNoUserAction");
+    }
+
+    @Test
+    public void newInstallDpc_withoutInstallDpcPermission_requiresUserAction() throws Exception {
+        runDeviceTests(
+                TEST_PKG, TEST_CLS,
+                "newInstallDpc_withoutInstallDpcPermission_requiresUserAction");
+    }
+
     private void waitForPathChange(String packageName, String originalCodePath, long timeout)
             throws DeviceNotAvailableException, InterruptedException {
                 long startTime = System.currentTimeMillis();
diff --git a/hostsidetests/silentupdate/testapp/Android.bp b/hostsidetests/silentupdate/testapp/Android.bp
index d3417e9..277198a 100644
--- a/hostsidetests/silentupdate/testapp/Android.bp
+++ b/hostsidetests/silentupdate/testapp/Android.bp
@@ -27,6 +27,8 @@
         "androidx.core_core",
         "androidx.test.runner",
         "truth-prebuilt",
+        "TestApp",
+        "Nene",
     ],
     sdk_version: "test_current",
     test_suites: [
diff --git a/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java b/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
index 4d72747..5326d6f 100644
--- a/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
+++ b/hostsidetests/silentupdate/testapp/src/com/android/tests/silentupdate/SilentUpdateTests.java
@@ -16,6 +16,7 @@
 
 package com.android.tests.silentupdate;
 
+import static android.Manifest.permission.INSTALL_DPC_PACKAGES;
 import static android.app.PendingIntent.FLAG_MUTABLE;
 import static android.content.Context.MODE_PRIVATE;
 import static android.content.pm.PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED;
@@ -40,6 +41,11 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.bedstead.testapp.TestApp;
+import com.android.bedstead.testapp.TestAppProvider;
+
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Test;
@@ -68,12 +74,19 @@
     private static final String INSTALLER_PACKAGE_NAME = "com.android.tests.silentupdate";
     static final long SILENT_UPDATE_THROTTLE_TIME_SECOND = 10;
 
+    private static final TestAppProvider sTestAppProvider = new TestAppProvider();
+    private static final TestApp sDpcApp = sTestAppProvider.query()
+                    .whereIsDeviceAdmin().isTrue()
+                    .whereTestOnly().isFalse()
+                    .get();
+
     private static Context getContext() {
         return InstrumentationRegistry.getInstrumentation().getContext();
     }
 
     @After
     public void tearDown() {
+        sDpcApp.uninstall();
         resetSilentUpdatesPolicy();
     }
 
@@ -209,6 +222,39 @@
                 silentInstallResource(CURRENT_APK));
     }
 
+    @Test
+    public void newInstall_withInstallDpcPermission_requiresUserAction() throws Exception {
+        try (PermissionContext p = TestApis.permissions().withPermission(
+                INSTALL_DPC_PACKAGES)) {
+            Assert.assertEquals("Installing a non DPC package with INSTALL_DPC_PACKAGES "
+                    + "permission should require user action.",
+                    PackageInstaller.STATUS_PENDING_USER_ACTION,
+                    silentInstallResource(CURRENT_APK));
+        }
+    }
+
+    @Test
+    public void newInstallDpc_withInstallDpcPermission_requiresNoUserAction() throws Exception {
+        try (PermissionContext p = TestApis.permissions().withPermission(
+                INSTALL_DPC_PACKAGES)) {
+            Assert.assertEquals("Installing a DPC package with INSTALL_DPC_PACKAGES "
+                            + "permission should not require user action.",
+                    PackageInstaller.STATUS_SUCCESS,
+                    install(sDpcApp::apkStream, /* requireUserAction= */ false));
+        }
+    }
+
+    @Test
+    public void newInstallDpc_withoutInstallDpcPermission_requiresUserAction() throws Exception {
+        try (PermissionContext p = TestApis.permissions().withoutPermission(
+                INSTALL_DPC_PACKAGES)) {
+            Assert.assertEquals("Installing a DPC package without INSTALL_DPC_PACKAGES "
+                            + "permission should require user action.",
+                    PackageInstaller.STATUS_PENDING_USER_ACTION,
+                    install(sDpcApp::apkStream, /* requireUserAction= */ false));
+        }
+    }
+
     private int silentInstallResource(String resourceName) throws Exception {
         return install(resourceSupplier(resourceName), false);
     }
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
index 712c563..7704070 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/ApexShimValidationTest.java
@@ -16,8 +16,8 @@
 
 package com.android.tests.stagedinstall;
 
+import static com.android.cts.install.lib.PackageInstallerSessionInfoSubject.assertThat;
 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
-import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
deleted file mode 100644
index fa0504d..0000000
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/PackageInstallerSessionInfoSubject.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.tests.stagedinstall;
-
-import android.content.pm.PackageInstaller;
-
-import com.google.common.truth.FailureMetadata;
-import com.google.common.truth.Subject;
-import com.google.common.truth.Truth;
-
-import javax.annotation.Nullable;
-
-final class PackageInstallerSessionInfoSubject extends Subject {
-    private final PackageInstaller.SessionInfo mActual;
-
-    private PackageInstallerSessionInfoSubject(FailureMetadata failureMetadata,
-            @Nullable PackageInstaller.SessionInfo subject) {
-        super(failureMetadata, subject);
-        mActual = subject;
-    }
-
-    private static Subject.Factory<PackageInstallerSessionInfoSubject,
-            PackageInstaller.SessionInfo> sessions() {
-        return new Subject.Factory<PackageInstallerSessionInfoSubject,
-                PackageInstaller.SessionInfo>() {
-            @Override
-            public PackageInstallerSessionInfoSubject createSubject(FailureMetadata failureMetadata,
-                    PackageInstaller.SessionInfo session) {
-                return new PackageInstallerSessionInfoSubject(failureMetadata, session);
-            }
-        };
-    }
-
-    static PackageInstallerSessionInfoSubject assertThat(
-            PackageInstaller.SessionInfo session) {
-        return Truth.assertAbout(sessions()).that(session);
-    }
-
-    public void isStagedSessionReady() {
-        check(failureMessage("in state READY")).that(mActual.isStagedSessionReady()).isTrue();
-    }
-
-    public void isStagedSessionApplied() {
-        check(failureMessage("in state APPLIED")).that(mActual.isStagedSessionApplied()).isTrue();
-    }
-
-    public void isStagedSessionFailed() {
-        check(failureMessage("in state FAILED")).that(mActual.isStagedSessionFailed()).isTrue();
-    }
-
-    private String failureMessage(String suffix) {
-        return String.format("Not true that session %s is %s", subjectAsString(), suffix);
-    }
-
-    private String subjectAsString() {
-        return "{" + "appPackageName = " + mActual.getAppPackageName() + "; "
-                + "sessionId = " + mActual.getSessionId() + "; "
-                + "isStagedSessionReady = " + mActual.isStagedSessionReady() + "; "
-                + "isStagedSessionApplied = " + mActual.isStagedSessionApplied() + "; "
-                + "isStagedSessionFailed = " + mActual.isStagedSessionFailed() + "; "
-                + "stagedSessionErrorMessage = " + mActual.getStagedSessionErrorMessage() + "}";
-    }
-}
diff --git a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
index e7d419b..6d37814 100644
--- a/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/app/src/com/android/tests/stagedinstall/StagedInstallTest.java
@@ -19,11 +19,11 @@
 import static com.android.cts.install.lib.InstallUtils.assertStatusFailure;
 import static com.android.cts.install.lib.InstallUtils.assertStatusSuccess;
 import static com.android.cts.install.lib.InstallUtils.getPackageInstaller;
+import static com.android.cts.install.lib.PackageInstallerSessionInfoSubject.assertThat;
 import static com.android.cts.shim.lib.ShimPackage.DIFFERENT_APEX_PACKAGE_NAME;
 import static com.android.cts.shim.lib.ShimPackage.NOT_PRE_INSTALL_APEX_PACKAGE_NAME;
 import static com.android.cts.shim.lib.ShimPackage.SHIM_APEX_PACKAGE_NAME;
 import static com.android.cts.shim.lib.ShimPackage.SHIM_PACKAGE_NAME;
-import static com.android.tests.stagedinstall.PackageInstallerSessionInfoSubject.assertThat;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -75,7 +75,9 @@
 import java.time.Duration;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
@@ -549,7 +551,7 @@
         int sessionId = stageSingleApk(Apex2DifferentPackageName).assertSuccessful().getSessionId();
         PackageInstaller.SessionInfo sessionInfo = waitForBroadcast(sessionId);
         assertThat(sessionInfo.getStagedSessionErrorMessage()).contains(
-                "It is forbidden to install new APEX packages.");
+                "Attempting to install new APEX package");
     }
 
     @Test
@@ -749,7 +751,6 @@
     public void testStagedApkSessionCallbacks() throws Exception {
 
         List<Integer> created = new ArrayList<Integer>();
-        List<Integer> finished = new ArrayList<Integer>();
 
         HandlerThread handlerThread = new HandlerThread(
                 "StagedApkSessionCallbacksTestHandlerThread");
@@ -768,13 +769,7 @@
             @Override public void onBadgingChanged(int sessionId) { }
             @Override public void onActiveChanged(int sessionId, boolean active) { }
             @Override public void onProgressChanged(int sessionId, float progress) { }
-
-            @Override
-            public void onFinished(int sessionId, boolean success) {
-                synchronized (finished) {
-                    finished.add(sessionId);
-                }
-            }
+            @Override public void onFinished(int sessionId, boolean success) { }
         };
 
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -795,9 +790,6 @@
         synchronized (created) {
             assertThat(created).containsExactly(sessionId);
         }
-        synchronized (finished) {
-            assertThat(finished).containsExactly(sessionId);
-        }
         packageInstaller.abandonSession(sessionId);
     }
 
@@ -1348,7 +1340,7 @@
 
         InstallUtils.commitExpectingFailure(
                     AssertionError.class,
-                    "Downgrade of APEX package com.android.apex.cts.shim is not allowed",
+                    "INSTALL_FAILED_VERSION_DOWNGRADE",
                     Install.single(Apex2Rebootless));
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(3);
     }
@@ -1370,7 +1362,7 @@
 
         InstallUtils.commitExpectingFailure(
                 AssertionError.class,
-                "It is forbidden to install new APEX packages",
+                "Attempting to install new APEX package",
                 Install.single(Apex2DifferentPackageName));
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
@@ -1415,7 +1407,7 @@
 
         InstallUtils.commitExpectingFailure(
                 AssertionError.class,
-                "Failed collecting certificates for",
+                "Failed to collect certificates from",
                 Install.single(Apex2NoApkSignature));
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
@@ -1431,6 +1423,23 @@
         assertThat(getInstalledVersion(SHIM_APEX_PACKAGE_NAME)).isEqualTo(1);
     }
 
+    @Test
+    public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
+            throws Exception {
+        int flags = (PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY
+                | PackageManager.MATCH_UNINSTALLED_PACKAGES);
+        List<PackageInfo> packageInfos =
+                InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
+                        .getInstalledPackages(flags);
+        Set<String> foundPackages = new HashSet<>();
+        for (PackageInfo pi : packageInfos) {
+            if (foundPackages.contains(pi.packageName)) {
+                throw new AssertionError(pi.packageName + " is listed at least twice.");
+            }
+            foundPackages.add(pi.packageName);
+        }
+    }
+
     // It becomes harder to maintain this variety of install-related helper methods.
     // TODO(ioffe): refactor install-related helper methods into a separate utility.
     private static int createStagedSession() throws Exception {
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index 8aed91b..e98eeda 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -824,6 +824,7 @@
     }
 
     @Test
+    @LargeTest
     public void testRebootlessUpdate_fromV2ToV3_sameBoot() throws Exception {
         assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
 
@@ -910,6 +911,16 @@
         runPhase("testRebootlessUpdate_targetsOlderSdk_fails");
     }
 
+    @Test
+    @LargeTest
+    public void testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates()
+            throws Exception {
+        assumeTrue("Device does not support updating APEX", mHostUtils.isApexUpdateSupported());
+
+        installV2Apex();
+        runPhase("testGetInactiveApexFactoryPackagesAfterApexInstall_containsNoDuplicates");
+    }
+
     private List<ApexInfo> readApexInfoList() throws Exception {
         File file = getDevice().pullFile("/apex/apex-info-list.xml");
         try (FileInputStream stream = new FileInputStream(file)) {
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 6f2da5f..1f793b3 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -36,6 +36,7 @@
         "src/**/gnss/*.java",
         "src/**/jobscheduler/*.java",
         "src/**/integrity/*.java",
+        "src/**/media/*.java",
         "src/**/memory/*.java",
         "src/**/net/*.java",
         "src/**/notification/*.java",
@@ -72,7 +73,11 @@
     data: [
         ":CtsStatsdAtomApp",
         ":CtsStatsdApp", //TODO(b/163546661): Remove once migration to new lib is complete.
-    ]
+        ":CtsAppExitTestCases",
+        ":CtsExternalServiceService",
+        ":CtsSimpleApp",
+    ],
+    per_testcase_directory: true,
 }
 
 java_library_host {
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 ef24a7a..9599f76 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
@@ -728,13 +728,36 @@
         builder.setOverrideDeadline(0);
         JobInfo job = builder.build();
 
-        long startTime = System.currentTimeMillis();
         CountDownLatch latch = StatsdJobService.resetCountDownLatch();
         js.schedule(job);
         waitForReceiver(context, 5_000, latch, null);
     }
 
     @Test
+    public void testScheduledJobPriority() throws Exception {
+        final ComponentName name =
+                new ComponentName(MY_PACKAGE_NAME, StatsdJobService.class.getName());
+
+        Context context = InstrumentationRegistry.getContext();
+        JobScheduler js = context.getSystemService(JobScheduler.class);
+        assertWithMessage("JobScheduler service not available").that(js).isNotNull();
+
+        final int[] priorities = {
+                JobInfo.PRIORITY_HIGH, JobInfo.PRIORITY_DEFAULT,
+                JobInfo.PRIORITY_LOW, JobInfo.PRIORITY_MIN};
+        for (int priority : priorities) {
+            JobInfo job = new JobInfo.Builder(priority, name)
+                    .setOverrideDeadline(0)
+                    .setPriority(priority)
+                    .build();
+
+            CountDownLatch latch = StatsdJobService.resetCountDownLatch();
+            js.schedule(job);
+            waitForReceiver(context, 5_000, latch, null);
+        }
+    }
+
+    @Test
     public void testVibratorState() {
         Context context = InstrumentationRegistry.getContext();
         Vibrator vib = context.getSystemService(Vibrator.class);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
index e8c42d1..9abd8c2 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appexit/AppExitHostTest.java
@@ -33,15 +33,23 @@
 import com.android.os.AtomsProto;
 import com.android.os.StatsLog;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.util.List;
 import java.util.function.Consumer;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class AppExitHostTest extends DeviceTestCase implements IBuildReceiver {
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class AppExitHostTest extends BaseHostJUnit4Test implements IBuildReceiver {
     private static final String TEST_PKG = "android.app.cts.appexit";
     private static final String HELPER_PKG1 = "android.externalservice.service";
     private static final String HELPER_PKG2 = "com.android.cts.launcherapps.simpleapp";
@@ -55,9 +63,8 @@
 
     private IBuildInfo mCtsBuild;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
+    @Before
+    public void setUp() throws Exception {
         assertThat(mCtsBuild).isNotNull();
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
@@ -71,8 +78,8 @@
         getDevice().executeShellCommand("pm grant " + TEST_PKG + " " + PERM_READ_LOGS);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
         getDevice().executeShellCommand("pm revoke " + TEST_PKG + " " + PERM_PACKAGE_USAGE_STATS);
@@ -80,7 +87,6 @@
         DeviceUtils.uninstallTestApp(getDevice(), TEST_PKG);
         DeviceUtils.uninstallTestApp(getDevice(), HELPER_PKG1);
         DeviceUtils.uninstallTestApp(getDevice(), HELPER_PKG2);
-        super.tearDown();
     }
 
     @Override
@@ -88,6 +94,7 @@
         mCtsBuild = buildInfo;
     }
 
+    @Test
     public void testLogStatsdPermChanged() throws Exception {
         final String helperPackage = HELPER_PKG2;
         final int expectedUid = getAppUid(helperPackage);
@@ -101,6 +108,7 @@
         });
     }
 
+    @Test
     public void testLogStatsdOther() throws Exception {
         final String helperPackage = HELPER_PKG1;
         final int expectedUid = getAppUid(TEST_PKG);
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
index e550db6..0ccc450 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appops/AppOpsTests.java
@@ -52,8 +52,7 @@
         final int APP_OP_RECORD_AUDIO = 27;
         final int APP_OP_RECORD_AUDIO_HOTWORD = 102;
 
-        // Temporarily commented out until the Trusted Hotword requirement is enforced again.
-//        TRANSFORMED_FROM_OP.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
+        TRANSFORMED_FROM_OP.put(APP_OP_RECORD_AUDIO, APP_OP_RECORD_AUDIO_HOTWORD);
     }
 
     private IBuildInfo mCtsBuild;
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
index ca75fc3..c84d60e 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/bluetooth/BluetoothStatsTests.java
@@ -82,7 +82,7 @@
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        AtomTestUtils.assertStatesOccurred(stateSet, data, expectedWait,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, expectedWait,
                 atom -> atom.getBleScanStateChanged().getState().getNumber());
     }
 
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
index d282ffb..efc1ae5 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/jobscheduler/JobSchedulerStatsTests.java
@@ -29,12 +29,18 @@
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
-import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
 public class JobSchedulerStatsTests extends DeviceTestCase implements IBuildReceiver {
+    private static final Set<Integer> STATE_SCHEDULE = new HashSet<>(
+            List.of(AtomsProto.ScheduledJobStateChanged.State.SCHEDULED_VALUE));
+    private static final Set<Integer> STATE_START = new HashSet<>(
+            List.of(AtomsProto.ScheduledJobStateChanged.State.STARTED_VALUE));
+    private static final Set<Integer> STATE_FINISH = new HashSet<>(
+            List.of(AtomsProto.ScheduledJobStateChanged.State.FINISHED_VALUE));
+
     private static final String JOB_NAME =
             "com.android.server.cts.device.statsdatom/.StatsdJobService";
 
@@ -65,15 +71,9 @@
 
     public void testScheduledJobState() throws Exception {
         final int atomTag = AtomsProto.Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
-        Set<Integer> jobSchedule = new HashSet<>(
-                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.SCHEDULED_VALUE));
-        Set<Integer> jobOn = new HashSet<>(
-                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.STARTED_VALUE));
-        Set<Integer> jobOff = new HashSet<>(
-                Arrays.asList(AtomsProto.ScheduledJobStateChanged.State.FINISHED_VALUE));
 
         // Add state sets to the list in order.
-        List<Set<Integer>> stateSet = Arrays.asList(jobSchedule, jobOn, jobOff);
+        List<Set<Integer>> stateSet = List.of(STATE_SCHEDULE, STATE_START, STATE_FINISH);
 
         ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
                 atomTag, /*useUidAttributionChain=*/true);
@@ -83,7 +83,7 @@
         // Sorted list of events in order in which they occurred.
         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0,
                 atom -> atom.getScheduledJobStateChanged().getState().getNumber());
 
         for (StatsLog.EventMetricData e : data) {
@@ -91,4 +91,35 @@
                     .isEqualTo(JOB_NAME);
         }
     }
+
+    public void testScheduledJobStatePriority() throws Exception {
+        final int atomTag = AtomsProto.Atom.SCHEDULED_JOB_STATE_CHANGED_FIELD_NUMBER;
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = List.of(
+                STATE_SCHEDULE, STATE_START, STATE_FINISH,
+                STATE_SCHEDULE, STATE_START, STATE_FINISH,
+                STATE_SCHEDULE, STATE_START, STATE_FINISH,
+                STATE_SCHEDULE, STATE_START, STATE_FINISH
+        );
+
+        ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                atomTag, /*useUidAttributionChain=*/true);
+        DeviceUtils.allowImmediateSyncs(getDevice());
+        DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests",
+                "testScheduledJobPriority");
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+
+        AtomTestUtils.assertStatesOccurred(stateSet, data,
+                atom -> atom.getScheduledJobStateChanged().getState().getNumber());
+
+        for (StatsLog.EventMetricData e : data) {
+            assertThat(e.getAtom().getScheduledJobStateChanged().getJobName())
+                    .isEqualTo(JOB_NAME);
+            assertThat(e.getAtom().getScheduledJobStateChanged().getRequestedPriority())
+                    .isEqualTo(e.getAtom().getScheduledJobStateChanged().getJobId());
+        }
+    }
 }
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
index dbb6699..3b2aa8c 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/AtomTestUtils.java
@@ -24,6 +24,7 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.log.LogUtil;
+import com.android.utils.SparseIntArray;
 
 import com.google.common.collect.Range;
 
@@ -56,6 +57,37 @@
     }
 
     /**
+     * Asserts that each set of states in {@code stateSets} occurs in {@code data} without assuming
+     * the order of occurrence.
+     *
+     * @param stateSets        A list of set of states, where each set represents an equivalent
+     *                         state of the device for the purpose of CTS.
+     * @param data             list of EventMetricData from statsd, produced by
+     *                         getReportMetricListData()
+     * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
+     */
+    public static void assertStatesOccurred(List<Set<Integer>> stateSets,
+            List<StatsLog.EventMetricData> data,
+            Function<AtomsProto.Atom, Integer> getStateFromAtom) {
+        // Sometimes, there are more events than there are states.
+        // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
+        assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
+        final SparseIntArray dataStateCount = new SparseIntArray();
+        for (StatsLog.EventMetricData emd : data) {
+            final int state = getStateFromAtom.apply(emd.getAtom());
+            dataStateCount.put(state, dataStateCount.get(state, 0) + 1);
+        }
+        for (Set<Integer> states : stateSets) {
+            for (int state : states) {
+                final int count = dataStateCount.get(state);
+                assertWithMessage("Remaining count of result state (%s)", state)
+                        .that(count).isGreaterThan(0);
+                dataStateCount.put(state, count - 1);
+            }
+        }
+    }
+
+    /**
      * Asserts that each set of states in stateSets occurs at least once in data.
      * Asserts that the states in data occur in the same order as the sets in stateSets.
      *
@@ -69,12 +101,12 @@
      *                         assertion.
      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
      */
-    public static void assertStatesOccurred(List<Set<Integer>> stateSets,
+    public static void assertStatesOccurredInOrder(List<Set<Integer>> stateSets,
             List<StatsLog.EventMetricData> data,
             int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) {
         // Sometimes, there are more events than there are states.
         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
-        assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
+        assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
             AtomsProto.Atom atom = data.get(dataIndex).getAtom();
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java
new file mode 100644
index 0000000..313afc5
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/MediaCapabilitiesTests.java
@@ -0,0 +1,178 @@
+/*
+ * 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.cts.statsdatom.media;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+import android.stats.mediametrics.Mediametrics;
+
+import com.android.os.AtomsProto;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+public class MediaCapabilitiesTests extends DeviceTestCase implements IBuildReceiver {
+    private static final String FEATURE_TV = "android.hardware.type.television";
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallStatsdTestApp(getDevice());
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testSurroundSoundCapabilities() throws Exception {
+        // Run this test only on TVs
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.MEDIA_CAPABILITIES_FIELD_NUMBER);
+
+        // Store the original value of settings
+        String isDtsEnabled = getDevice().executeShellCommand(
+                "cmd audio get-is-surround-format-enabled 7").split(":")[1].trim();
+        String isDolbyTrueHdEnabled = getDevice().executeShellCommand(
+                "cmd audio get-is-surround-format-enabled 14").split(":")[1].trim();
+        String encodedSurroundMode = getDevice().executeShellCommand(
+                "cmd audio get-encoded-surround-mode").split(":")[1].trim();
+
+        // Setting the values of audio setting via shell commands
+        getDevice().executeShellCommand(
+                "cmd audio set-surround-format-enabled 7 true");
+        getDevice().executeShellCommand(
+                "cmd audio set-surround-format-enabled 14 false");
+        getDevice().executeShellCommand("cmd audio set-encoded-surround-mode 2");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Trigger atom pull.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // The list of atoms will be empty if the atom is not supported.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getMediaCapabilities().getSurroundEncodings().getAudioEncodingsCount())
+                    .isAtLeast(1);
+            assertEquals(Mediametrics.EncodedSurroundOutputMode.ENCODED_SURROUND_OUTPUT_NEVER,
+                    atom.getMediaCapabilities().getSurroundOutputMode());
+            assertThat(Mediametrics.AudioEncoding.ENCODING_DTS).isIn(
+                    atom.getMediaCapabilities()
+                            .getUserEnabledSurroundEncodings().getAudioEncodingsList());
+            assertThat(Mediametrics.AudioEncoding.ENCODING_DOLBY_TRUEHD).isNotIn(
+                    atom.getMediaCapabilities()
+                            .getUserEnabledSurroundEncodings().getAudioEncodingsList());
+        }
+
+        // Restore the original value of settings
+        getDevice().executeShellCommand(
+                "cmd audio set-surround-format-enabled 7 " + isDtsEnabled);
+        getDevice().executeShellCommand(
+                "cmd audio set-surround-format-enabled 14 " + isDolbyTrueHdEnabled);
+        getDevice().executeShellCommand(
+                "cmd audio set-encoded-surround-mode " + encodedSurroundMode);
+    }
+
+    public void testDisplayCapabilities() throws Exception {
+        // Run this test only on TVs
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) return;
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.MEDIA_CAPABILITIES_FIELD_NUMBER);
+
+        // Store the original value of settings
+        String userPrefDisplayMode = getDevice().executeShellCommand(
+                "cmd display get-user-preferred-display-mode").split(":")[1].trim();
+        String matchContentFrameRatePref = getDevice().executeShellCommand(
+                "cmd display get-match-content-frame-rate-pref").split(":")[1].trim();
+        String userDisabledHdrTypes = getDevice().executeShellCommand(
+                "cmd display get-user-disabled-hdr-types").split(":")[1].trim();
+        userDisabledHdrTypes.replaceAll(", ", " ");
+
+
+        // Setting the values of display setting via shell commands
+        getDevice().executeShellCommand(
+                "cmd display set-user-preferred-display-mode 720 1020 60.0f");
+        getDevice().executeShellCommand("cmd display set-match-content-frame-rate-pref 2");
+        getDevice().executeShellCommand("cmd display set-user-disabled-hdr-types 0 1");
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Trigger atom pull.
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        // The list of atoms will be empty if the atom is not supported.
+        List<AtomsProto.Atom> atoms = ReportUtils.getGaugeMetricAtoms(getDevice());
+
+        for (AtomsProto.Atom atom : atoms) {
+            assertThat(atom.getMediaCapabilities().getSinkDisplayModes().getDisplayModesCount())
+                    .isAtLeast(1);
+            assertEquals(720, atom.getMediaCapabilities().getUserPreferredResolutionHeight());
+            assertEquals(1020, atom.getMediaCapabilities().getUserPreferredResolutionWidth());
+            assertEquals(60.0f, atom.getMediaCapabilities().getUserPreferredRefreshRate());
+            assertEquals(Mediametrics.MatchContentFrameRatePreference
+                            .MATCH_CONTENT_FRAMERATE_SEAMLESSS_ONLY,
+                    atom.getMediaCapabilities().getMatchContentRefreshRatePreference());
+            assertThat(
+                    atom.getMediaCapabilities().getUserDisabledHdrFormats().getHdrFormats(0))
+                    .isAnyOf(
+                            Mediametrics.HdrFormat.HDR_TYPE_DOLBY_VISION,
+                            Mediametrics.HdrFormat.HDR_TYPE_HDR10);
+            assertThat(
+                    atom.getMediaCapabilities().getUserDisabledHdrFormats().getHdrFormats(1))
+                    .isAnyOf(
+                            Mediametrics.HdrFormat.HDR_TYPE_DOLBY_VISION,
+                            Mediametrics.HdrFormat.HDR_TYPE_HDR10);
+        }
+
+        // Restore the original value of settings
+        if (userPrefDisplayMode.equals("null")) {
+            getDevice().executeShellCommand(
+                    "cmd display clear-user-preferred-display-mode");
+        } else {
+            getDevice().executeShellCommand(
+                    "cmd display set-user-preferred-display-mode " + userPrefDisplayMode);
+        }
+        getDevice().executeShellCommand("cmd display set-match-content-frame-rate-pref "
+                + matchContentFrameRatePref);
+        getDevice().executeShellCommand("cmd display set-user-disabled-hdr-types "
+                + userDisabledHdrTypes);
+    }
+}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS
new file mode 100644
index 0000000..5af70bb
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/media/OWNERS
@@ -0,0 +1,2 @@
+kritidang@google.com
+blindahl@google.com
\ No newline at end of file
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
index ee1d0e0..8b6f991 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/HostAtomTests.java
@@ -28,16 +28,12 @@
 import android.platform.test.annotations.RestrictedBuildTest;
 import android.server.DeviceIdleModeEnum;
 import android.view.DisplayStateEnum;
-import android.telephony.NetworkTypeEnum;
 
-import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto.AppBreadcrumbReported;
 import com.android.os.AtomsProto.Atom;
 import com.android.os.AtomsProto.BatterySaverModeStateChanged;
 import com.android.os.AtomsProto.BuildInformation;
 import com.android.os.AtomsProto.ConnectivityStateChanged;
-import com.android.os.AtomsProto.SimSlotState;
-import com.android.os.AtomsProto.SupportedRadioAccessFamily;
 import com.android.os.StatsLog.ConfigMetricsReportList;
 import com.android.os.StatsLog.EventMetricData;
 import com.android.tradefed.build.IBuildInfo;
@@ -50,15 +46,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Map;
-import java.util.Queue;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  * Statsd atom tests that are done via adb (hostside).
@@ -149,7 +139,7 @@
         // Restores AoD to initial state.
         setAodState(aodState);
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
                 atom -> atom.getScreenStateChanged().getState().getNumber());
     }
 
@@ -199,7 +189,7 @@
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getChargingStateChanged().getState().getNumber());
     }
 
@@ -255,7 +245,7 @@
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
                 atom -> atom.getPluggedStateChanged().getState().getNumber());
     }
 
@@ -263,6 +253,7 @@
         if (DeviceUtils.hasFeature(getDevice(), FEATURE_AUTOMOTIVE)) return;
         // Setup, set battery level to full.
         setBatteryLevel(100);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         final int atomTag = Atom.BATTERY_LEVEL_CHANGED_FIELD_NUMBER;
@@ -282,15 +273,20 @@
 
         // Trigger events in same order.
         setBatteryLevel(2);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
         setBatteryLevel(25);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
         setBatteryLevel(50);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
         setBatteryLevel(75);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
         setBatteryLevel(100);
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+        DeviceUtils.flushBatteryStatsHandlers(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
 
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
@@ -300,7 +296,7 @@
         Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
                 atom -> atom.getBatteryLevelChanged().getBatteryLevel());
     }
 
@@ -336,7 +332,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getDeviceIdleModeStateChanged().getState().getNumber());
     }
 
@@ -370,7 +366,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
                 atom -> atom.getBatterySaverModeStateChanged().getState().getNumber());
     }
 
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
index 11353ce..2608ca8 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/ProcStateAtomTests.java
@@ -158,7 +158,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         AtomTestUtils.popUntilFind(data, onStates,
                 PROC_STATE_FUNCTION); // clear out initial proc states.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
     }
 
     public void testForeground() throws Exception {
@@ -177,7 +177,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         AtomTestUtils.popUntilFind(data, onStates,
                 PROC_STATE_FUNCTION); // clear out initial proc states.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, PROC_STATE_FUNCTION);
     }
 
     public void testBackground() throws Exception {
@@ -195,7 +195,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         AtomTestUtils.popUntilFind(data, onStates,
                 PROC_STATE_FUNCTION); // clear out initial proc states.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
     }
 
     public void testTop() throws Exception {
@@ -214,7 +214,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
         AtomTestUtils.popUntilFind(data, onStates,
                 PROC_STATE_FUNCTION); // clear out initial proc states.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, waitTime, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, waitTime, PROC_STATE_FUNCTION);
     }
 
     public void testTopSleeping() throws Exception {
@@ -244,7 +244,7 @@
         // reset screen back on
         DeviceUtils.turnScreenOn(getDevice());
         // Don't check the wait time, since it's up to the system how long top sleeping persists.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, PROC_STATE_FUNCTION);
     }
 
     public void testCached() throws Exception {
@@ -276,7 +276,7 @@
         // Now clear out the bg state from step #2 (since we are interested in the cache after it).
         AtomTestUtils.popUntilFind(data, onStates, PROC_STATE_FUNCTION);
         // The result is that data should start at step #3, definitively in a cached state.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 1_000, PROC_STATE_FUNCTION);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 1_000, PROC_STATE_FUNCTION);
     }
 
     public void testValidityOfStates() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 0a00c67..0cf7fce 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -57,8 +57,8 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.util.Pair;
 
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
@@ -308,11 +308,15 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_LONG,
                 atom -> atom.getCameraStateChanged().getState().getNumber());
     }
 
     public void testDeviceCalculatedPowerUse() throws Exception {
+        if (DeviceUtils.hasFeature(getDevice(), FEATURE_TV)) {
+            // Skip TVs because they do not have batteries.
+            return;
+        }
         if (!DeviceUtils.hasFeature(getDevice(), FEATURE_LEANBACK_ONLY)) return;
 
         ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
@@ -394,7 +398,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getFlashlightStateChanged().getState().getNumber());
     }
 
@@ -419,7 +423,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getForegroundServiceStateChanged().getState().getNumber());
     }
 
@@ -540,7 +544,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, videoDuration,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, videoDuration,
                 atom -> atom.getMediaCodecStateChanged().getState().getNumber());
     }
 
@@ -568,7 +572,7 @@
 
         // Assert that the events happened in the expected order.
         // The overlay box should appear about 2sec after the app start
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 0,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0,
                 atom -> atom.getOverlayStateChanged().getState().getNumber());
     }
 
@@ -659,7 +663,7 @@
         AtomTestUtils.popUntilFindFromEnd(data, screen140,
                 atom -> atom.getScreenBrightnessChanged().getLevel());
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getScreenBrightnessChanged().getLevel());
     }
 
@@ -680,7 +684,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data,
                 /* wait = */ 0 /* don't verify time differences between state changes */,
                 atom -> atom.getSyncStateChanged().getState().getNumber());
     }
@@ -708,7 +712,7 @@
         // Sorted list of events in order in which they occurred.
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 300,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 300,
                 atom -> atom.getVibratorStateChanged().getState().getNumber());
     }
 
@@ -735,7 +739,7 @@
         List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getWakelockStateChanged().getState().getNumber());
 
         for (EventMetricData event : data) {
@@ -877,7 +881,7 @@
                 atom -> atom.getAppUsageEventOccurred().getEventType().getNumber();
         // clear out initial appusage states
         AtomTestUtils.popUntilFind(data, onStates, appUsageStateFunction);
-        AtomTestUtils.assertStatesOccurred(stateSet, data, 0, appUsageStateFunction);
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, 0, appUsageStateFunction);
     }
 /*
     public void testAppForceStopUsageEvent() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
index 8a00db4..87c29c9 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/telephony/TelephonyStatsTests.java
@@ -263,6 +263,21 @@
         assertThat(data).isNotEmpty();
     }
 
+    public void testPerSimStatus() throws Exception {
+        if (!DeviceUtils.hasFeature(getDevice(), FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+                AtomsProto.Atom.PER_SIM_STATUS_FIELD_NUMBER);
+
+        AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+        List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+        assertThat(data).hasSize(getActiveSimSlotCount());
+    }
+
     private boolean hasGsmPhone() throws Exception {
         // Not using log entries or ServiceState in the dump since they may or may not be present,
         // which can make the test flaky
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
index c4bec178..cb3df79 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/wifi/WifiStatsTests.java
@@ -91,7 +91,7 @@
         List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getWifiLockStateChanged().getState().getNumber());
 
         for (StatsLog.EventMetricData event : data) {
@@ -120,7 +120,7 @@
         List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getWifiLockStateChanged().getState().getNumber());
 
         for (StatsLog.EventMetricData event : data) {
@@ -152,7 +152,7 @@
         List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff);
 
         // Assert that the events happened in the expected order.
-        AtomTestUtils.assertStatesOccurred(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT,
                 atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber());
 
         for (StatsLog.EventMetricData event : data) {
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index 6de52cd..6fd9c8c 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -29,6 +29,7 @@
 
     static_libs: [
         "cts-statsd-atom-host-test-utils",
+        "platformprotos",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/systemui/OWNERS b/hostsidetests/systemui/OWNERS
index 7e823ba..a48e7e3 100644
--- a/hostsidetests/systemui/OWNERS
+++ b/hostsidetests/systemui/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 136515
-kozynski@google.com
-dsandler@android.com
-ashaikh@google.com
+# Bug component: 78010
+per-file *TileService* = file:/tests/quicksettings/OWNERS
+per-file *Notification* = juliacr@google.com
+dsandler@android.com #{LAST_RESORT_SUGGESTION}
diff --git a/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java b/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
index a0cb026..bde3390 100644
--- a/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
+++ b/hostsidetests/testharness/app/src/android/testharness/app/TestHarnessModeDeviceTest.java
@@ -30,6 +30,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 /** Device-side tests for Test Harness Mode */
 @RunWith(AndroidJUnit4.class)
@@ -51,7 +52,7 @@
     @Test
     public void dirtyDevice() {
         TestHarnessActivity activity = mActivityRule.getActivity();
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
 
         activity.dirtyDevice();
     }
@@ -59,7 +60,7 @@
     @Test
     public void testDeviceIsClean() {
         TestHarnessActivity activity = mActivityRule.getActivity();
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
 
         Assert.assertTrue(activity.isDeviceClean());
     }
diff --git a/hostsidetests/theme/assets/31/450dpi.zip b/hostsidetests/theme/assets/31/450dpi.zip
deleted file mode 100644
index 5ce9a1e..0000000
--- a/hostsidetests/theme/assets/31/450dpi.zip
+++ /dev/null
Binary files differ
diff --git a/hostsidetests/theme/assets/S/450dpi.zip b/hostsidetests/theme/assets/S/450dpi.zip
new file mode 100755
index 0000000..e346378
--- /dev/null
+++ b/hostsidetests/theme/assets/S/450dpi.zip
Binary files differ
diff --git a/hostsidetests/time/Android.bp b/hostsidetests/time/Android.bp
index 72d27e4..79d85d1 100644
--- a/hostsidetests/time/Android.bp
+++ b/hostsidetests/time/Android.bp
@@ -24,6 +24,9 @@
         "cts-statsd-atom-host-test-utils",
         "host_time_shell_utils",
     ],
+    data: [
+        ":CtsFakeTimeZoneProvidersApp",
+    ],
     test_suites: ["general-tests", "cts"],
     test_config: "host/AndroidTest.xml",
 }
diff --git a/hostsidetests/time/device/fake_tzps_app/Android.bp b/hostsidetests/time/device/fake_tzps_app/Android.bp
new file mode 100644
index 0000000..c17e49d
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// An app that hosts fake TimeZoneProviderService implementations that can be used by tests to query
+// the fake providers' state and inject test events into the fake providers.
+android_test_helper_app {
+    name: "CtsFakeTimeZoneProvidersApp",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    static_libs: [
+        "device_time_shell_utils",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml b/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml
new file mode 100644
index 0000000..80898ff
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?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.time.cts.fake_tzps_app">
+
+    <application android:debuggable="true"
+                 android:allowBackup="false">
+        <!-- A content provider that can be used to interact with the fake providers. -->
+        <provider android:name=".fixture.FakeTimeZoneProviderFixtureProvider"
+                  android:authorities="faketzpsapp"
+                  android:multiprocess="false"
+                  android:exported="true" />
+
+        <!-- A primary location time zone provider. -->
+        <service android:name=".tzps.FakeLocationTimeZoneProviderService1"
+                 android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.timezone.PrimaryLocationTimeZoneProviderService" />
+            </intent-filter>
+            <meta-data android:name="serviceIsMultiuser" android:value="true" />
+        </service>
+
+        <!-- A secondary location time zone provider. -->
+        <service android:name=".tzps.FakeLocationTimeZoneProviderService2"
+                 android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.service.timezone.SecondaryLocationTimeZoneProviderService" />
+            </intent-filter>
+            <meta-data android:name="serviceIsMultiuser" android:value="true" />
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java
new file mode 100644
index 0000000..886768e
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderFixtureProvider.java
@@ -0,0 +1,125 @@
+/*
+ * 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.time.cts.fake_tzps_app.fixture;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import com.android.time.cts.fake_tzps_app.tzps.FakeTimeZoneProviderService;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A {@link ContentProvider} that can interact with the fake {@link
+ * android.service.timezone.TimeZoneProviderService} implementations. This enables test code to
+ * interact with the fakes: device-side code can use the usual content provider APIs, and both
+ * device-side and host-side code can use the "adb shell content" command. For simplicity,
+ * everything is implemented using the "call" verb.
+ */
+public class FakeTimeZoneProviderFixtureProvider extends ContentProvider {
+
+    private static final String METHOD_GET_STATE = "get_state";
+    private static final String CALL_RESULT_KEY_GET_STATE_STATE = "state";
+    private static final String METHOD_REPORT_PERMANENT_FAILURE = "perm_fail";
+    private static final String METHOD_REPORT_UNCERTAIN = "uncertain";
+    private static final String METHOD_REPORT_SUCCESS = "success";
+    private static final String METHOD_PING = "ping";
+
+    /** Suggestion time zone IDs. A single string, comma separated, may be empty. */
+    private static final String CALL_EXTRA_KEY_SUGGESTION_ZONE_IDS = "zone_ids";
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder) {
+        // No impl
+        return null;
+    }
+
+    @Override
+    public String getType(Uri uri) {
+        // No impl
+        return null;
+    }
+
+    @Override
+    public Uri insert(Uri uri, ContentValues values) {
+        // No impl
+        return null;
+    }
+
+    @Override
+    public int delete(Uri uri, String selection, String[] selectionArgs) {
+        // No impl
+        return 0;
+    }
+
+    @Override
+    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        // No impl
+        return 0;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        Objects.requireNonNull(extras);
+
+        if (METHOD_PING.equals(method)) {
+            // No-op - this method just exists to make sure the content provider exists.
+            return Bundle.EMPTY;
+        }
+
+        String providerId = Objects.requireNonNull(arg);
+        FakeTimeZoneProviderService provider = FakeTimeZoneProviderRegistry.getInstance()
+                .getFakeTimeZoneProviderService(providerId);
+        Objects.requireNonNull(provider, "arg=" + providerId + ", provider not found");
+
+        Bundle result = new Bundle();
+        switch (method) {
+            case METHOD_GET_STATE: {
+                result.putInt(CALL_RESULT_KEY_GET_STATE_STATE, provider.getState());
+                break;
+            }
+            case METHOD_REPORT_PERMANENT_FAILURE: {
+                provider.fakeReportPermanentFailure();
+                break;
+            }
+            case METHOD_REPORT_UNCERTAIN: {
+                provider.fakeReportUncertain();
+                break;
+            }
+            case METHOD_REPORT_SUCCESS: {
+                String zoneIdsString = extras.getString(CALL_EXTRA_KEY_SUGGESTION_ZONE_IDS);
+                List<String> zoneIds = Arrays.asList(zoneIdsString.split(","));
+                provider.fakeReportSuggestion(zoneIds);
+                break;
+            }
+            default: {
+                throw new IllegalArgumentException("method=" + method + " not known");
+            }
+        }
+        return result;
+    }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java
new file mode 100644
index 0000000..420efda
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/fixture/FakeTimeZoneProviderRegistry.java
@@ -0,0 +1,49 @@
+/*
+ * 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.time.cts.fake_tzps_app.fixture;
+
+import com.android.time.cts.fake_tzps_app.tzps.FakeTimeZoneProviderService;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A singleton registry of fake {@link android.service.timezone.TimeZoneProviderService} instances.
+ */
+public class FakeTimeZoneProviderRegistry {
+
+    private static final FakeTimeZoneProviderRegistry sInstance =
+            new FakeTimeZoneProviderRegistry();
+
+    private final Map<String, FakeTimeZoneProviderService> fakeTimeZoneProviderServiceMap =
+            new HashMap<>();
+
+    private FakeTimeZoneProviderRegistry() {
+    }
+
+    public static FakeTimeZoneProviderRegistry getInstance() {
+        return sInstance;
+    }
+
+    public synchronized void registerFakeTimeZoneProviderService(String id,
+            FakeTimeZoneProviderService fakeTimeZoneProviderService) {
+        fakeTimeZoneProviderServiceMap.put(id, fakeTimeZoneProviderService);
+    }
+
+    public synchronized FakeTimeZoneProviderService getFakeTimeZoneProviderService(String id) {
+        return fakeTimeZoneProviderServiceMap.get(id);
+    }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.java
new file mode 100644
index 0000000..6591168
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService1.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.time.cts.fake_tzps_app.tzps;
+
+/** The fake primary location time zone provider. */
+public class FakeLocationTimeZoneProviderService1 extends FakeTimeZoneProviderService {
+
+    public FakeLocationTimeZoneProviderService1() {
+        super(FakeLocationTimeZoneProviderService1.class.getSimpleName());
+    }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.java
new file mode 100644
index 0000000..6bb7d36
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeLocationTimeZoneProviderService2.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.time.cts.fake_tzps_app.tzps;
+
+/** The fake secondary location time zone provider. */
+public class FakeLocationTimeZoneProviderService2 extends FakeTimeZoneProviderService {
+
+    public FakeLocationTimeZoneProviderService2() {
+        super(FakeLocationTimeZoneProviderService2.class.getSimpleName());
+    }
+}
diff --git a/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java
new file mode 100644
index 0000000..36bd58f
--- /dev/null
+++ b/hostsidetests/time/device/fake_tzps_app/src/com/android/time/cts/fake_tzps_app/tzps/FakeTimeZoneProviderService.java
@@ -0,0 +1,81 @@
+/*
+ * 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.time.cts.fake_tzps_app.tzps;
+
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_CERTAIN;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_DISABLED;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_INITIALIZING;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_PERM_FAILED;
+
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.os.SystemClock;
+import android.service.timezone.TimeZoneProviderService;
+import android.service.timezone.TimeZoneProviderSuggestion;
+
+import com.android.time.cts.fake_tzps_app.fixture.FakeTimeZoneProviderRegistry;
+
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A base class for fake implementations of {@link TimeZoneProviderService} that can be queried /
+ * poked during tests. Each instance registers itself with {@link FakeTimeZoneProviderRegistry} on
+ * construction to enable interaction from tests.
+ */
+public class FakeTimeZoneProviderService extends TimeZoneProviderService {
+
+    private final String mId;
+    private int mState = PROVIDER_STATE_DISABLED;
+
+    protected FakeTimeZoneProviderService(String id) {
+        mId = Objects.requireNonNull(id);
+        FakeTimeZoneProviderRegistry.getInstance().registerFakeTimeZoneProviderService(id, this);
+    }
+
+    @Override
+    public void onStartUpdates(long initializationTimeoutMillis) {
+        mState = PROVIDER_STATE_INITIALIZING;
+    }
+
+    @Override
+    public void onStopUpdates() {
+        mState = PROVIDER_STATE_DISABLED;
+    }
+
+    // Fake behavior methods.
+    public int getState() {
+        return mState;
+    }
+
+    public void fakeReportUncertain() {
+        reportUncertain();
+        mState = FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_UNCERTAIN;
+    }
+
+    public void fakeReportPermanentFailure() {
+        reportPermanentFailure(new RuntimeException("Fake permanent failure"));
+        mState = PROVIDER_STATE_PERM_FAILED;
+    }
+
+    public void fakeReportSuggestion(List<String> timeZoneIds) {
+        TimeZoneProviderSuggestion suggestion = new TimeZoneProviderSuggestion.Builder()
+                .setTimeZoneIds(timeZoneIds)
+                .setElapsedRealtimeMillis(SystemClock.elapsedRealtime())
+                .build();
+        reportSuggestion(suggestion);
+        mState = PROVIDER_STATE_CERTAIN;
+    }
+}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java
deleted file mode 100644
index 1cc28e6..0000000
--- a/hostsidetests/time/host/src/android/time/cts/host/BaseLocationTimeZoneManagerHostTest.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * 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.time.cts.host;
-
-import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
-
-import android.app.time.LocationTimeZoneManagerServiceStateProto;
-import android.app.time.cts.shell.DeviceConfigShellHelper;
-import android.app.time.cts.shell.DeviceShellCommandExecutor;
-import android.app.time.cts.shell.LocationShellHelper;
-import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
-import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
-import android.app.time.cts.shell.host.HostShellCommandExecutor;
-
-import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
-import com.google.protobuf.Parser;
-
-import org.junit.After;
-import org.junit.Before;
-
-/** A base class for tests that interact with the location_time_zone_manager via adb. */
-public abstract class BaseLocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
-
-    private boolean mOriginalLocationEnabled;
-
-    private boolean mOriginalAutoDetectionEnabled;
-
-    private boolean mOriginalGeoDetectionEnabled;
-
-    protected TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
-    private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
-    private LocationShellHelper mLocationShellHelper;
-    private DeviceConfigShellHelper mDeviceConfigShellHelper;
-    private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
-
-    @Before
-    public void setUp() throws Exception {
-        DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(getDevice());
-        mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
-        mLocationTimeZoneManagerShellHelper =
-                new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
-        mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
-        mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
-
-        // Confirm the service being tested is present. It can be turned off, in which case there's
-        // nothing to test.
-        mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
-
-        // All tests start with the location_time_zone_manager disabled so that providers can be
-        // configured.
-        stopLocationTimeZoneManagerService();
-
-        mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
-                DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
-
-        // Configure two simulated providers. At least one is needed to be able to turn on
-        // geo detection below. Tests may override these values for their own use.
-        setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
-        setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
-
-        // Make sure locations is enabled, otherwise the geo detection feature will be disabled
-        // whatever the geolocation detection setting is set to.
-        mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
-        if (!mOriginalLocationEnabled) {
-            mLocationShellHelper.setLocationEnabledForCurrentUser(true);
-        }
-
-        // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
-        // will be disabled whatever the geolocation detection setting is set to
-        mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
-        if (!mOriginalAutoDetectionEnabled) {
-            mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
-        }
-
-        // Make sure geolocation time zone detection is enabled.
-        mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
-        if (!mOriginalGeoDetectionEnabled) {
-            mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
-            // Setup didn't do anything, no need to tearDown.
-            return;
-        }
-        // Turn off the service before we reset configuration, otherwise it will restart itself
-        // repeatedly.
-        stopLocationTimeZoneManagerService();
-
-        // Reset settings and server flags as best we can.
-        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
-        mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
-        mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
-        setLocationTimeZoneManagerStateRecordingMode(false);
-
-        mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
-
-        // Attempt to start the service. It may not start if there are no providers configured,
-        // but that is ok.
-        startLocationTimeZoneManagerService();
-    }
-
-    protected LocationTimeZoneManagerServiceStateProto dumpLocationTimeZoneManagerServiceState()
-            throws Exception {
-        byte[] protoBytes = mLocationTimeZoneManagerShellHelper.dumpState();
-        Parser<LocationTimeZoneManagerServiceStateProto> parser =
-                LocationTimeZoneManagerServiceStateProto.parser();
-        return parser.parseFrom(protoBytes);
-    }
-
-    protected void setLocationTimeZoneManagerStateRecordingMode(boolean enabled) throws Exception {
-        mLocationTimeZoneManagerShellHelper.recordProviderStates(enabled);
-    }
-
-    protected void startLocationTimeZoneManagerService() throws Exception {
-        mLocationTimeZoneManagerShellHelper.start();
-    }
-
-    protected void stopLocationTimeZoneManagerService() throws Exception {
-        mLocationTimeZoneManagerShellHelper.stop();
-    }
-
-    protected void setProviderModeOverride(int providerIndex, String mode) throws Exception {
-        mLocationTimeZoneManagerShellHelper.setProviderModeOverride(providerIndex, mode);
-    }
-
-    protected void simulateProviderSuggestion(int providerIndex, String... zoneIds)
-            throws Exception {
-        mLocationTimeZoneManagerShellHelper.simulateProviderSuggestion(providerIndex, zoneIds);
-    }
-
-    protected void simulateProviderBind(int providerIndex) throws Exception {
-        mLocationTimeZoneManagerShellHelper.simulateProviderBind(providerIndex);
-    }
-}
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
index 1894805..76f4723 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerHostTest.java
@@ -17,48 +17,481 @@
 package android.time.cts.host;
 
 
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_DISABLED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
+import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS;
+import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_CERTAIN;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_DISABLED;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_INITIALIZING;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.PROVIDER_STATE_UNCERTAIN;
 
 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.app.time.ControllerStateEnum;
 import android.app.time.LocationTimeZoneManagerServiceStateProto;
 import android.app.time.TimeZoneProviderStateEnum;
 import android.app.time.TimeZoneProviderStateProto;
+import android.app.time.cts.shell.DeviceConfigShellHelper;
+import android.app.time.cts.shell.DeviceShellCommandExecutor;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FakeTimeZoneProviderShellHelper;
+import android.app.time.cts.shell.LocationShellHelper;
+import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
+import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
+import com.google.protobuf.Parser;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
 
 /** Host-side CTS tests for the location time zone manager service. */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class LocationTimeZoneManagerHostTest extends BaseLocationTimeZoneManagerHostTest {
+public class LocationTimeZoneManagerHostTest extends BaseHostJUnit4Test {
 
+    private boolean mOriginalLocationEnabled;
+    private boolean mOriginalAutoDetectionEnabled;
+    private boolean mOriginalGeoDetectionEnabled;
+    private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
+    private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
+    private DeviceConfigShellHelper mDeviceConfigShellHelper;
+    private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
+    private LocationShellHelper mLocationShellHelper;
+    private FakeTimeZoneProviderShellHelper mPrimaryFakeTimeZoneProviderShellHelper;
+    private FakeTimeZoneProviderShellHelper mSecondaryFakeTimeZoneProviderShellHelper;
+
+    @Before
+    public void setUp() throws Exception {
+        DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(getDevice());
+        mLocationTimeZoneManagerShellHelper =
+                new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
+
+        // Confirm the service being tested is present. It can be turned off, in which case there's
+        // nothing to test.
+        mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
+
+        // Install the app that hosts the fake providers.
+        // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
+        installPackage(FAKE_TZPS_APP_APK);
+
+        mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
+        mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
+        mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
+
+        // Stop device_config updates for the duration of the test.
+        mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
+                SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+
+        // All tests start with the location_time_zone_manager disabled so that providers can be
+        // configured.
+        mLocationTimeZoneManagerShellHelper.stop();
+
+        // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+        // whatever the geolocation detection setting is set to.
+        mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
+        if (!mOriginalLocationEnabled) {
+            mLocationShellHelper.setLocationEnabledForCurrentUser(true);
+        }
+
+        // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+        // will be disabled whatever the geolocation detection setting is set to.
+        mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
+        if (!mOriginalAutoDetectionEnabled) {
+            mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
+        }
+
+        // On devices with no location time zone providers (e.g. AOSP), we cannot turn geo detection
+        // on until the test LTZPs are configured as the time_zone_detector will refuse.
+        mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
+
+        FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
+                new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
+        // Delay until the fake TZPS app can be found.
+        fakeTimeZoneProviderAppShellHelper.waitForInstallation();
+        mPrimaryFakeTimeZoneProviderShellHelper =
+                fakeTimeZoneProviderAppShellHelper.getPrimaryLocationProviderHelper();
+        mSecondaryFakeTimeZoneProviderShellHelper =
+                fakeTimeZoneProviderAppShellHelper.getSecondaryLocationProviderHelper();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
+            // Nothing to tear down.
+            return;
+        }
+
+        // Reset the geoDetectionEnabled state while there is at least one LTZP configured: this
+        // setting cannot be modified if there are no LTZPs on the device, e.g. on AOSP.
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
+
+        // Turn off the service before we reset configuration, otherwise it will restart itself
+        // repeatedly.
+        mLocationTimeZoneManagerShellHelper.stop();
+
+        // Reset settings and server flags as best we can.
+        mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+        mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
+        mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
+
+        // Attempt to start the service. It may not start if there are no providers configured,
+        // but that is ok.
+        mLocationTimeZoneManagerShellHelper.start();
+    }
+
+    /** Tests what happens when there's only a primary provider and it makes a suggestion. */
     @Test
-    public void testSecondarySuggestion() throws Exception {
-        setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_DISABLED);
-        setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
-        startLocationTimeZoneManagerService();
-        setLocationTimeZoneManagerStateRecordingMode(true);
+    public void testOnlyPrimary_suggestionMade() throws Exception {
+        String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        String testSecondaryLocationTimeZoneProviderPackageName = null;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates */);
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+        mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+        mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
 
-        simulateProviderBind(SECONDARY_PROVIDER_INDEX);
-        simulateProviderSuggestion(SECONDARY_PROVIDER_INDEX, "Europe/London");
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+                    ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+                    ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+            assertNoLastSuggestion(serviceState);
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
 
-        LocationTimeZoneManagerServiceStateProto serviceState =
-                dumpLocationTimeZoneManagerServiceState();
-        assertEquals(Arrays.asList("Europe/London"),
-                serviceState.getLastSuggestion().getZoneIdsList());
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+        }
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
 
-        List<TimeZoneProviderStateProto> secondaryStates =
-                serviceState.getSecondaryProviderStatesList();
-        assertEquals(1, secondaryStates.size());
-        assertEquals(TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN,
-                secondaryStates.get(0).getState());
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+            assertLastSuggestion(serviceState, "Europe/London");
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList());
+        }
+    }
+
+    /**
+     * Demonstrates that duplicate equivalent reports made by location time zone providers within
+     * a threshold time are ignored. It focuses on a single LTZP setup (primary only); the behavior
+     * for the secondary is assumed to be identical.
+     */
+    @Test
+    public void test_dupeSuggestionsMade_rateLimited() throws Exception {
+        // Set the rate setting sufficiently high that rate limiting will definitely take place.
+        mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
+                KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+                Long.toString(Duration.ofMinutes(10).toMillis()));
+
+        String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        String testSecondaryLocationTimeZoneProviderPackageName = null;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates */);
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+
+        mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+        mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report a new time zone.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+        assertPrimaryReportedCertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Duplicate time zone suggestion.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+        assertPrimaryMadeNoReport();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report a new time zone.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+        assertPrimaryReportedCertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Duplicate time zone suggestion.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+        assertPrimaryMadeNoReport();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report uncertain.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+        assertPrimaryReportedUncertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Duplicate uncertain report.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+        assertPrimaryMadeNoReport();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report a new time zone.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+        assertPrimaryReportedCertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+    }
+
+    /**
+     * Demonstrates that duplicate equivalent reports made by location time zone providers above
+     * a threshold time are not filtered. It focuses on a single LTZP setup (primary only); the
+     * behavior for the secondary is assumed to be identical.
+     */
+    @Test
+    public void test_dupeSuggestionsMade_notRateLimited() throws Exception {
+        // Set the rate sufficiently low that rate limiting will not take place.
+        mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME,
+                KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS,
+                "0");
+
+        String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        String testSecondaryLocationTimeZoneProviderPackageName = null;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates */);
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+        mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+        mSecondaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report a new time zone.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+        assertPrimaryReportedCertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Duplicate time zone suggestion.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+        assertPrimaryReportedCertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Report uncertain.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+        assertPrimaryReportedUncertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Duplicate uncertain report.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+        assertPrimaryReportedUncertain();
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+    }
+
+    private void assertPrimaryReportedCertain() throws Exception {
+        LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+        assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+    }
+
+    private void assertPrimaryMadeNoReport() throws Exception {
+        LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+        assertProviderStates(serviceState.getPrimaryProviderStatesList());
+    }
+
+    private void assertPrimaryReportedUncertain() throws Exception {
+        LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+        assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
+    }
+
+    /** Tests what happens when there's only a secondary provider and it makes a suggestion. */
+    @Test
+    public void testOnlySecondary_suggestionMade() throws Exception {
+        String testPrimaryLocationTimeZoneProviderPackageName = null;
+        String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates */);
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+        mPrimaryFakeTimeZoneProviderShellHelper.assertNotCreated();
+        mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+                    ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+                    ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+            assertNoLastSuggestion(serviceState);
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING,
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_PERM_FAILED);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+        }
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        mSecondaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+            assertLastSuggestion(serviceState, "Europe/London");
+            assertProviderStates(serviceState.getPrimaryProviderStatesList());
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+            mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+        }
+    }
+
+    /**
+     * Tests what happens when there's both a primary and a secondary provider, the primary starts
+     * by being uncertain, the secondary makes a suggestion, then the primary makes a suggestion.
+     */
+    @Test
+    public void testPrimaryAndSecondary() throws Exception {
+        String testPrimaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates*/);
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
+        mPrimaryFakeTimeZoneProviderShellHelper.assertCreated();
+        mSecondaryFakeTimeZoneProviderShellHelper.assertCreated();
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_PROVIDERS_INITIALIZING,
+                    ControllerStateEnum.CONTROLLER_STATE_STOPPED,
+                    ControllerStateEnum.CONTROLLER_STATE_INITIALIZING);
+            assertNoLastSuggestion(serviceState);
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED,
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_INITIALIZING);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+            mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
+        }
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Make the primary report being uncertain. This should cause the secondary to be started.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportUncertain();
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState);
+            assertNoLastSuggestion(serviceState);
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_UNCERTAIN);
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_INITIALIZING);
+            mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(
+                    PROVIDER_STATE_INITIALIZING);
+        }
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Make the secondary report being certain.
+        mSecondaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/London");
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState,
+                    ControllerStateEnum.CONTROLLER_STATE_CERTAIN);
+            assertLastSuggestion(serviceState, "Europe/London");
+            assertProviderStates(serviceState.getPrimaryProviderStatesList());
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_UNCERTAIN);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+            mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+        }
+        mLocationTimeZoneManagerShellHelper.clearRecordedProviderStates();
+
+        // Make the primary report being certain.
+        mPrimaryFakeTimeZoneProviderShellHelper.reportSuccess("Europe/Paris");
+
+        {
+            LocationTimeZoneManagerServiceStateProto serviceState = dumpServiceState();
+            assertControllerStateHistory(serviceState);
+            assertLastSuggestion(serviceState, "Europe/Paris");
+            assertProviderStates(serviceState.getPrimaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_CERTAIN);
+            mPrimaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_CERTAIN);
+
+            assertProviderStates(serviceState.getSecondaryProviderStatesList(),
+                    TimeZoneProviderStateEnum.TIME_ZONE_PROVIDER_STATE_DISABLED);
+            mSecondaryFakeTimeZoneProviderShellHelper.assertCurrentState(PROVIDER_STATE_DISABLED);
+        }
+    }
+
+    private static void assertControllerStateHistory(
+            LocationTimeZoneManagerServiceStateProto serviceState,
+            ControllerStateEnum... expectedStates) {
+        List<ControllerStateEnum> expectedStatesList = Arrays.asList(expectedStates);
+        List<ControllerStateEnum> actualStates = serviceState.getControllerStatesList();
+        assertEquals(expectedStatesList, actualStates);
+    }
+
+    private static void assertNoLastSuggestion(
+            LocationTimeZoneManagerServiceStateProto serviceState) {
+        if (serviceState.hasLastSuggestion()) {
+            fail("Expected no last suggestion, but found:" + serviceState.getLastSuggestion());
+        }
+    }
+
+    private static void assertLastSuggestion(LocationTimeZoneManagerServiceStateProto serviceState,
+            String... expectedTimeZones) {
+        assertFalse(expectedTimeZones == null || expectedTimeZones.length == 0);
+        assertTrue(serviceState.hasLastSuggestion());
+        List<String> expectedTimeZonesList = Arrays.asList(expectedTimeZones);
+        List<String> actualTimeZonesList = serviceState.getLastSuggestion().getZoneIdsList();
+        assertEquals(expectedTimeZonesList, actualTimeZonesList);
+    }
+
+    private static void assertProviderStates(List<TimeZoneProviderStateProto> actualStates,
+            TimeZoneProviderStateEnum... expectedStates) {
+        List<TimeZoneProviderStateEnum> expectedStatesList = Arrays.asList(expectedStates);
+        assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
+                expectedStatesList.size(), actualStates.size());
+        for (int i = 0; i < expectedStatesList.size(); i++) {
+            assertEquals("Expected states: " + expectedStatesList + ", but was " + actualStates,
+                    expectedStates[i], actualStates.get(i).getState());
+        }
+    }
+
+    private LocationTimeZoneManagerServiceStateProto dumpServiceState() throws Exception {
+        byte[] protoBytes = mLocationTimeZoneManagerShellHelper.dumpState();
+        Parser<LocationTimeZoneManagerServiceStateProto> parser =
+                LocationTimeZoneManagerServiceStateProto.parser();
+        return parser.parseFrom(protoBytes);
     }
 }
diff --git a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
index d24b060..0be4769 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/LocationTimeZoneManagerStatsTest.java
@@ -16,13 +16,20 @@
 
 package android.time.cts.host;
 
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PRIMARY_PROVIDER_INDEX;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_DISABLED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.PROVIDER_MODE_SIMULATED;
-import static android.app.time.cts.shell.LocationTimeZoneManagerShellHelper.SECONDARY_PROVIDER_INDEX;
+import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_APK;
+import static android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper.FAKE_TZPS_APP_PACKAGE;
 
 import static java.util.stream.Collectors.toList;
 
+import android.app.time.cts.shell.DeviceConfigShellHelper;
+import android.app.time.cts.shell.DeviceShellCommandExecutor;
+import android.app.time.cts.shell.FakeTimeZoneProviderAppShellHelper;
+import android.app.time.cts.shell.LocationShellHelper;
+import android.app.time.cts.shell.LocationTimeZoneManagerShellHelper;
+import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
 import android.cts.statsdatom.lib.AtomTestUtils;
 import android.cts.statsdatom.lib.ConfigUtils;
 import android.cts.statsdatom.lib.DeviceUtils;
@@ -31,7 +38,9 @@
 import com.android.os.AtomsProto;
 import com.android.os.AtomsProto.LocationTimeZoneProviderStateChanged;
 import com.android.os.StatsLog;
+import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
 import org.junit.After;
 import org.junit.Before;
@@ -46,54 +55,137 @@
 
 /** Host-side CTS tests for the location time zone manager service stats logging. */
 @RunWith(DeviceJUnit4ClassRunner.class)
-public class LocationTimeZoneManagerStatsTest extends BaseLocationTimeZoneManagerHostTest {
+public class LocationTimeZoneManagerStatsTest extends BaseHostJUnit4Test {
+
+    private static final int PRIMARY_PROVIDER_INDEX = 0;
+    private static final int SECONDARY_PROVIDER_INDEX = 1;
 
     private static final int PROVIDER_STATES_COUNT =
             LocationTimeZoneProviderStateChanged.State.values().length;
 
+    private TimeZoneDetectorShellHelper mTimeZoneDetectorShellHelper;
+    private LocationTimeZoneManagerShellHelper mLocationTimeZoneManagerShellHelper;
+    private LocationShellHelper mLocationShellHelper;
+    private DeviceConfigShellHelper mDeviceConfigShellHelper;
+    private DeviceConfigShellHelper.PreTestState mDeviceConfigPreTestState;
+
+    private boolean mOriginalLocationEnabled;
+    private boolean mOriginalAutoDetectionEnabled;
+    private boolean mOriginalGeoDetectionEnabled;
+
     @Before
-    @Override
     public void setUp() throws Exception {
-        super.setUp();
-        ConfigUtils.removeConfig(getDevice());
-        ReportUtils.clearReports(getDevice());
+        ITestDevice device = getDevice();
+        DeviceShellCommandExecutor shellCommandExecutor = new HostShellCommandExecutor(device);
+        mLocationTimeZoneManagerShellHelper =
+                new LocationTimeZoneManagerShellHelper(shellCommandExecutor);
+
+        // Confirm the service being tested is present. It can be turned off, in which case there's
+        // nothing to test.
+        mLocationTimeZoneManagerShellHelper.assumeLocationTimeZoneManagerIsPresent();
+
+        // Install the app that hosts the fake providers.
+        // Installations are tracked in BaseHostJUnit4Test and uninstalled automatically.
+        installPackage(FAKE_TZPS_APP_APK);
+
+        mTimeZoneDetectorShellHelper = new TimeZoneDetectorShellHelper(shellCommandExecutor);
+        mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
+        mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
+
+        mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
+                SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+
+        // All tests start with the location_time_zone_manager disabled so that providers can be
+        // configured.
+        mLocationTimeZoneManagerShellHelper.stop();
+
+        // Make sure locations is enabled, otherwise the geo detection feature will be disabled
+        // whatever the geolocation detection setting is set to.
+        mOriginalLocationEnabled = mLocationShellHelper.isLocationEnabledForCurrentUser();
+        if (!mOriginalLocationEnabled) {
+            mLocationShellHelper.setLocationEnabledForCurrentUser(true);
+        }
+
+        // Make sure automatic time zone detection is enabled, otherwise the geo detection feature
+        // will be disabled whatever the geolocation detection setting is set to
+        mOriginalAutoDetectionEnabled = mTimeZoneDetectorShellHelper.isAutoDetectionEnabled();
+        if (!mOriginalAutoDetectionEnabled) {
+            mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(true);
+        }
+
+        // On devices with no location time zone providers (e.g. AOSP), we cannot turn geo detection
+        // on until the test LTZPs are configured as the time_zone_detector will refuse.
+        mOriginalGeoDetectionEnabled = mTimeZoneDetectorShellHelper.isGeoDetectionEnabled();
+
+        // Make sure that the fake providers used in the tests are available.
+        FakeTimeZoneProviderAppShellHelper fakeTimeZoneProviderAppShellHelper =
+                new FakeTimeZoneProviderAppShellHelper(shellCommandExecutor);
+        fakeTimeZoneProviderAppShellHelper.waitForInstallation();
+
+        ConfigUtils.removeConfig(device);
+        ReportUtils.clearReports(device);
     }
 
     @After
-    @Override
     public void tearDown() throws Exception {
+        if (!mLocationTimeZoneManagerShellHelper.isLocationTimeZoneManagerPresent()) {
+            // Nothing to tear down.
+            return;
+        }
+
+        // Reset the geoDetectionEnabled state while there is at least one LTZP configured: this
+        // setting cannot be modified if there are no LTZPs on the device, e.g. on AOSP.
+        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(mOriginalGeoDetectionEnabled);
+
+        // Turn off the service before we reset configuration, otherwise it will restart itself
+        // repeatedly.
+        mLocationTimeZoneManagerShellHelper.stop();
+
+        // Reset settings and server flags as best we can.
+        mTimeZoneDetectorShellHelper.setAutoDetectionEnabled(mOriginalAutoDetectionEnabled);
+        mLocationShellHelper.setLocationEnabledForCurrentUser(mOriginalLocationEnabled);
+
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
-        super.tearDown();
+        mDeviceConfigShellHelper.restoreDeviceConfigStateForTest(mDeviceConfigPreTestState);
+
+        // Attempt to start the service. It may not start if there are no providers configured,
+        // but that is ok.
+        mLocationTimeZoneManagerShellHelper.start();
     }
 
     @Test
     public void testAtom_locationTimeZoneProviderStateChanged() throws Exception {
-        setProviderModeOverride(PRIMARY_PROVIDER_INDEX, PROVIDER_MODE_DISABLED);
-        setProviderModeOverride(SECONDARY_PROVIDER_INDEX, PROVIDER_MODE_SIMULATED);
-        mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(false);
-
-        startLocationTimeZoneManagerService();
-
         ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
                 AtomsProto.Atom.LOCATION_TIME_ZONE_PROVIDER_STATE_CHANGED_FIELD_NUMBER);
 
+        String testPrimaryLocationTimeZoneProviderPackageName = null;
+        String testSecondaryLocationTimeZoneProviderPackageName = FAKE_TZPS_APP_PACKAGE;
+        mLocationTimeZoneManagerShellHelper.startWithTestProviders(
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                true /* recordProviderStates */);
+
         // Turn geo detection on and off, twice.
         for (int i = 0; i < 2; i++) {
+            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
             mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(true);
             Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
             mTimeZoneDetectorShellHelper.setGeoDetectionEnabled(false);
-            Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
         }
 
         // Sorted list of events in order in which they occurred.
         List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
 
         // States.
+        Set<Integer> primaryProviderCreated = singletonStateId(PRIMARY_PROVIDER_INDEX,
+                LocationTimeZoneProviderStateChanged.State.STOPPED);
         Set<Integer> primaryProviderStarted = singletonStateId(PRIMARY_PROVIDER_INDEX,
                 LocationTimeZoneProviderStateChanged.State.INITIALIZING);
         Set<Integer> primaryProviderFailed = singletonStateId(PRIMARY_PROVIDER_INDEX,
                 LocationTimeZoneProviderStateChanged.State.PERM_FAILED);
+        Set<Integer> secondaryProviderCreated = singletonStateId(SECONDARY_PROVIDER_INDEX,
+                LocationTimeZoneProviderStateChanged.State.STOPPED);
         Set<Integer> secondaryProviderStarted = singletonStateId(SECONDARY_PROVIDER_INDEX,
                 LocationTimeZoneProviderStateChanged.State.INITIALIZING);
         Set<Integer> secondaryProviderStopped = singletonStateId(SECONDARY_PROVIDER_INDEX,
@@ -108,21 +200,12 @@
         // Assert that the events happened in the expected order. This does not check "wait" (the
         // time between events).
         List<Set<Integer>> stateSets = Arrays.asList(
+                primaryProviderCreated, secondaryProviderCreated,
                 primaryProviderStarted, primaryProviderFailed,
                 secondaryProviderStarted, secondaryProviderStopped,
                 secondaryProviderStarted, secondaryProviderStopped);
-        AtomTestUtils.assertStatesOccurred(stateSets, data,
+        AtomTestUtils.assertStatesOccurredInOrder(stateSets, data,
                 0 /* wait */, eventToStateFunction);
-
-        // Assert that the events for the secondary provider happened in the expected order. This
-        // does check "wait" (the time between events).
-        List<StatsLog.EventMetricData> secondaryEvents =
-                extractEventsForProviderIndex(data, SECONDARY_PROVIDER_INDEX);
-        List<Set<Integer>> secondaryStateSets = Arrays.asList(
-                secondaryProviderStarted, secondaryProviderStopped,
-                secondaryProviderStarted, secondaryProviderStopped);
-        AtomTestUtils.assertStatesOccurred(secondaryStateSets, secondaryEvents,
-                AtomTestUtils.WAIT_TIME_SHORT /* wait */, eventToStateFunction);
     }
 
     private static Set<Integer> singletonStateId(int providerIndex,
diff --git a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
index 92992f7..4b8a83c 100644
--- a/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
+++ b/hostsidetests/time/host/src/android/time/cts/host/TimeZoneDetectorStatsTest.java
@@ -17,6 +17,7 @@
 package android.time.cts.host;
 
 import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
+import static android.app.time.cts.shell.DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -24,8 +25,8 @@
 import android.app.time.cts.shell.DeviceConfigShellHelper;
 import android.app.time.cts.shell.DeviceShellCommandExecutor;
 import android.app.time.cts.shell.LocationShellHelper;
-import android.app.time.cts.shell.host.HostShellCommandExecutor;
 import android.app.time.cts.shell.TimeZoneDetectorShellHelper;
+import android.app.time.cts.shell.host.HostShellCommandExecutor;
 import android.cts.statsdatom.lib.AtomTestUtils;
 import android.cts.statsdatom.lib.ConfigUtils;
 import android.cts.statsdatom.lib.DeviceUtils;
@@ -59,7 +60,7 @@
         mLocationShellHelper = new LocationShellHelper(shellCommandExecutor);
         mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
         mDeviceConfigPreTestState = mDeviceConfigShellHelper.setSyncModeForTest(
-                DeviceConfigShellHelper.SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
+                SYNC_DISABLED_MODE_UNTIL_REBOOT, NAMESPACE_SYSTEM_TIME);
 
         ConfigUtils.removeConfig(getDevice());
         ReportUtils.clearReports(getDevice());
diff --git a/libs/input/src/com/android/cts/input/UinputDevice.java b/libs/input/src/com/android/cts/input/UinputDevice.java
index 5a1441c..b2db4e0 100644
--- a/libs/input/src/com/android/cts/input/UinputDevice.java
+++ b/libs/input/src/com/android/cts/input/UinputDevice.java
@@ -75,6 +75,19 @@
     }
 
     /**
+     * Create Uinput device using the provided resourceId.
+     */
+    public static UinputDevice create(Instrumentation instrumentation, int resourceId,
+            int sources) {
+        final InputJsonParser parser = new InputJsonParser(instrumentation.getTargetContext());
+        final int resourceDeviceId = parser.readDeviceId(resourceId);
+        final String registerCommand = parser.readRegisterCommand(resourceId);
+        return new UinputDevice(instrumentation, resourceDeviceId,
+                parser.readVendorId(resourceId), parser.readProductId(resourceId),
+                sources, registerCommand);
+    }
+
+    /**
      * Get uinput command return results as list of UinputResultData
      *
      * @return List of UinputResultData results
diff --git a/libs/input/src/com/android/cts/input/VirtualInputDevice.java b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
index 5c8879c..2fd4154 100644
--- a/libs/input/src/com/android/cts/input/VirtualInputDevice.java
+++ b/libs/input/src/com/android/cts/input/VirtualInputDevice.java
@@ -198,6 +198,18 @@
         return mDeviceId;
     }
 
+    public int getRegisterCommandDeviceId() {
+        return mId;
+    }
+
+    public int getVendorId() {
+        return mVendorId;
+    }
+
+    public int getProductId() {
+        return mProductId;
+    }
+
     private void setupPipes() {
         UiAutomation ui = mInstrumentation.getUiAutomation();
         ParcelFileDescriptor[] pipes = ui.executeShellCommandRw(getShellCommand());
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index 4e2c335..31784e4 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -132,6 +132,31 @@
     apex_available: [ "com.android.apex.apkrollback.test_v2" ],
 }
 
+android_test_helper_app {
+    name: "TestAppARollbackWipeV2",
+    manifest: "testapp/ARollbackWipeV2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+    apex_available: [ "com.android.apex.apkrollback.test_v2" ],
+}
+
+android_test_helper_app {
+    name: "TestAppBRollbackRestoreV2",
+    manifest: "testapp/BRollbackRestoreV2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
+android_test_helper_app {
+    name: "TestAppCRollbackRetainV2",
+    manifest: "testapp/CRollbackRetainV2.xml",
+    sdk_version: "current",
+    srcs: ["testapp/src/**/*.java"],
+    resource_dirs: ["testapp/res_v2"],
+}
+
 java_library {
     name: "cts-install-lib-java",
     srcs: ["src/**/lib/*.java"],
@@ -156,6 +181,9 @@
         ":TestAppASplitV2",
         ":TestAppAOriginalV1",
         ":TestAppARotatedV2",
+        ":TestAppARollbackWipeV2",
+        ":TestAppBRollbackRestoreV2",
+        ":TestAppCRollbackRetainV2",
         ":StagedInstallTestApexV1",
         ":StagedInstallTestApexV2",
         ":StagedInstallTestApexV3",
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index 377e88a2..86113e6 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -20,6 +20,7 @@
 
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
+import android.text.TextUtils;
 
 import com.android.compatibility.common.util.SystemUtil;
 
@@ -38,6 +39,7 @@
     // Indicates whether Install represents a multiPackage install.
     private final boolean mIsMultiPackage;
     // PackageInstaller.Session parameters.
+    private String mPackageName = null;
     private boolean mIsStaged = false;
     private boolean mIsDowngrade = false;
     private boolean mEnableRollback = false;
@@ -96,6 +98,14 @@
     }
 
     /**
+     * Sets package name to the session params.
+     */
+    public Install setPackageName(String packageName) {
+        mPackageName = packageName;
+        return this;
+    }
+
+    /**
      * Makes the install a staged install.
      */
     public Install setStaged() {
@@ -227,6 +237,9 @@
         try {
             PackageInstaller.SessionParams params =
                     new PackageInstaller.SessionParams(mSessionMode);
+            if (!TextUtils.isEmpty(mPackageName)) {
+                params.setAppPackageName(mPackageName);
+            }
             if (multiPackage) {
                 params.setMultiPackage();
             }
diff --git a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
index cdf709c..85b1ac6 100644
--- a/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
+++ b/libs/install/src/com/android/cts/install/lib/LocalIntentSender.java
@@ -54,7 +54,8 @@
         Context context = InstrumentationRegistry.getTargetContext();
         // Generate a unique string to ensure each LocalIntentSender gets its own results.
         String action = LocalIntentSender.class.getName() + SystemClock.elapsedRealtime();
-        context.registerReceiver(this, new IntentFilter(action));
+        context.registerReceiver(this, new IntentFilter(action),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
         Intent intent = new Intent(action);
         PendingIntent pending = PendingIntent.getBroadcast(context, 0, intent, FLAG_MUTABLE);
         return pending.getIntentSender();
diff --git a/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java b/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java
new file mode 100644
index 0000000..74c75eb
--- /dev/null
+++ b/libs/install/src/com/android/cts/install/lib/PackageInstallerSessionInfoSubject.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.install.lib;
+
+import android.content.pm.PackageInstaller;
+
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import javax.annotation.Nullable;
+
+public final class PackageInstallerSessionInfoSubject extends Subject {
+    private final PackageInstaller.SessionInfo mActual;
+
+    private PackageInstallerSessionInfoSubject(FailureMetadata failureMetadata,
+            @Nullable PackageInstaller.SessionInfo subject) {
+        super(failureMetadata, subject);
+        mActual = subject;
+    }
+
+    private static Subject.Factory<PackageInstallerSessionInfoSubject,
+            PackageInstaller.SessionInfo> sessions() {
+        return new Subject.Factory<PackageInstallerSessionInfoSubject,
+                PackageInstaller.SessionInfo>() {
+            @Override
+            public PackageInstallerSessionInfoSubject createSubject(FailureMetadata failureMetadata,
+                    PackageInstaller.SessionInfo session) {
+                return new PackageInstallerSessionInfoSubject(failureMetadata, session);
+            }
+        };
+    }
+
+    public static PackageInstallerSessionInfoSubject assertThat(
+            PackageInstaller.SessionInfo session) {
+        return Truth.assertAbout(sessions()).that(session);
+    }
+
+    public void isStagedSessionReady() {
+        check(failureMessage("in state READY")).that(mActual.isStagedSessionReady()).isTrue();
+    }
+
+    public void isStagedSessionApplied() {
+        check(failureMessage("in state APPLIED")).that(mActual.isStagedSessionApplied()).isTrue();
+    }
+
+    public void isStagedSessionFailed() {
+        check(failureMessage("in state FAILED")).that(mActual.isStagedSessionFailed()).isTrue();
+    }
+
+    private String failureMessage(String suffix) {
+        return String.format("Not true that session %s is %s", subjectAsString(), suffix);
+    }
+
+    private String subjectAsString() {
+        return "{" + "appPackageName = " + mActual.getAppPackageName() + "; "
+                + "sessionId = " + mActual.getSessionId() + "; "
+                + "isStagedSessionReady = " + mActual.isStagedSessionReady() + "; "
+                + "isStagedSessionApplied = " + mActual.isStagedSessionApplied() + "; "
+                + "isStagedSessionFailed = " + mActual.isStagedSessionFailed() + "; "
+                + "stagedSessionErrorMessage = " + mActual.getStagedSessionErrorMessage() + "}";
+    }
+}
diff --git a/libs/install/src/com/android/cts/install/lib/TestApp.java b/libs/install/src/com/android/cts/install/lib/TestApp.java
index 8775ef4..8e550d9 100644
--- a/libs/install/src/com/android/cts/install/lib/TestApp.java
+++ b/libs/install/src/com/android/cts/install/lib/TestApp.java
@@ -54,6 +54,8 @@
             "TestAppAOriginalV1.apk");
     public static final TestApp ARotated2 = new TestApp("ARotatedV2", A, 2, /*isApex*/false,
             "TestAppARotatedV2.apk");
+    public static final TestApp ARollbackWipe2 = new TestApp("ARollbackWipe2", A, 2,
+            /*isApex*/false, "TestAppARollbackWipeV2.apk");
 
     public static final TestApp B1 = new TestApp("Bv1", B, 1, /*isApex*/false,
             "TestAppBv1.apk");
@@ -61,11 +63,15 @@
             "TestAppBv2.apk");
     public static final TestApp B3 = new TestApp("Bv3", B, 3, /*isApex*/false,
             "TestAppBv3.apk");
+    public static final TestApp BRollbackRestore2 = new TestApp("BRollbackRestore2", B, 2,
+            /*isApex*/false, "TestAppBRollbackRestoreV2.apk");
 
     public static final TestApp C1 = new TestApp("Cv1", C, 1, /*isApex*/false,
             "TestAppCv1.apk");
     public static final TestApp C2 = new TestApp("Cv2", C, 2, /*isApex*/false,
             "TestAppCv2.apk");
+    public static final TestApp CRollbackRetain2 = new TestApp("CRollbackRetain2", C, 2,
+            /*isApex*/false, "TestAppCRollbackRetainV2.apk");
 
     // Apex collection
     public static final TestApp Apex1 = new TestApp("Apex1", SHIM_APEX_PACKAGE_NAME, 1,
diff --git a/libs/install/testapp/ARollbackWipeV2.xml b/libs/install/testapp/ARollbackWipeV2.xml
new file mode 100644
index 0000000..047efb5
--- /dev/null
+++ b/libs/install/testapp/ARollbackWipeV2.xml
@@ -0,0 +1,37 @@
+<?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.install.lib.testapp.A"
+     android:versionCode="2"
+     android:versionName="2.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App A2"
+        android:rollbackDataPolicy="wipe">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/BRollbackRestoreV2.xml b/libs/install/testapp/BRollbackRestoreV2.xml
new file mode 100644
index 0000000..29aa674
--- /dev/null
+++ b/libs/install/testapp/BRollbackRestoreV2.xml
@@ -0,0 +1,37 @@
+<?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.install.lib.testapp.B"
+     android:versionCode="2"
+     android:versionName="2.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App B2"
+        android:rollbackDataPolicy="restore">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/CRollbackRetainV2.xml b/libs/install/testapp/CRollbackRetainV2.xml
new file mode 100644
index 0000000..d1d168d
--- /dev/null
+++ b/libs/install/testapp/CRollbackRetainV2.xml
@@ -0,0 +1,38 @@
+<?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.install.lib.testapp.C"
+     android:versionCode="2"
+     android:versionName="2.0">
+
+
+    <uses-sdk android:minSdkVersion="19"/>
+
+    <application android:label="Test App C2"
+         android:rollbackDataPolicy="retain">
+        <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
+             android:exported="true"/>
+        <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/libs/install/testapp/Cv2.xml b/libs/install/testapp/Cv2.xml
index 0d98b63..cc13ed5 100644
--- a/libs/install/testapp/Cv2.xml
+++ b/libs/install/testapp/Cv2.xml
@@ -23,8 +23,7 @@
 
     <uses-sdk android:minSdkVersion="28"/>
 
-    <application android:label="Test App C2"
-         android:rollbackDataPolicy="wipe">
+    <application android:label="Test App C2">
         <receiver android:name="com.android.cts.install.lib.testapp.ProcessUserData"
              android:exported="true"/>
         <activity android:name="com.android.cts.install.lib.testapp.MainActivity"
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
index c1de522..595dd8be 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -266,7 +266,8 @@
                 latch.countDown();
             }
         };
-        context.registerReceiver(crashReceiver, crashFilter);
+        context.registerReceiver(crashReceiver, crashFilter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         // Launch the app.
         Intent intent = new Intent(Intent.ACTION_MAIN);
diff --git a/tests/AlarmManager/app/AndroidManifest.xml b/tests/AlarmManager/app/AndroidManifest.xml
index 9725080..618a1a0 100644
--- a/tests/AlarmManager/app/AndroidManifest.xml
+++ b/tests/AlarmManager/app/AndroidManifest.xml
@@ -31,6 +31,7 @@
              where this is not required -->
             <intent-filter>
                 <action android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+                <action android:name="android.app.action.cts.ACTION_PING" />
             </intent-filter>
         </receiver>
         <service android:name=".TestService"
diff --git a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
index a556c4e..9615cad 100644
--- a/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/ExactAlarmsTest.java
@@ -530,7 +530,7 @@
                         PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                         PackageManager.DONT_KILL_APP));
         Log.d(TAG, "Un-force-stoppping the test app");
-        Intent i = new Intent("ACTION_PING"); // any action
+        Intent i = new Intent("android.app.action.cts.ACTION_PING");
         i.setComponent(mPermissionChangeReceiver);
         i.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         sContext.sendBroadcast(i);
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index 9fa8601..618c324 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -33,6 +33,7 @@
 import junit.framework.Assert;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -76,6 +77,9 @@
     public boolean onStartJob(JobParameters params) {
         Log.i(TAG, "Test job executing: " + params.getJobId());
         mParams = params;
+        TestEnvironment.getTestEnvironment().addEvent(
+                new TestEnvironment.Event(
+                        TestEnvironment.Event.EVENT_START_JOB, params.getJobId()));
 
         int permCheckRead = PackageManager.PERMISSION_DENIED;
         int permCheckWrite = PackageManager.PERMISSION_DENIED;
@@ -380,6 +384,7 @@
         private ArrayList<JobWorkItem> mExecutedReceivedWork;
         private String mExecutedErrorMessage;
         private JobParameters mStopJobParameters;
+        private List<Event> mExecutedEvents = new ArrayList<>();
 
         public static TestEnvironment getTestEnvironment() {
             if (kTestEnvironment == null) {
@@ -466,7 +471,6 @@
 
         private void notifyExecution(JobParameters params, int permCheckRead, int permCheckWrite,
                 ArrayList<JobWorkItem> receivedWork, String errorMsg) {
-            //Log.d(TAG, "Job executed:" + params.getJobId());
             mExecutedJobParameters = params;
             mExecutedPermCheckRead = permCheckRead;
             mExecutedPermCheckWrite = permCheckWrite;
@@ -501,6 +505,7 @@
             mDoWorkLatch = null;
             mExpectedWork = null;
             mContinueAfterStart = false;
+            mExecutedEvents.clear();
         }
 
         public void setExpectedWaitForStop() {
@@ -545,5 +550,46 @@
             mStopJobParameters = null;
         }
 
+        void addEvent(Event event) {
+            mExecutedEvents.add(event);
+        }
+
+        public List<Event> getExecutedEvents() {
+            return mExecutedEvents;
+        }
+
+        public static class Event {
+            public static final int EVENT_START_JOB = 0;
+
+            public int event;
+            public int jobId;
+
+            public Event(int event, int jobId) {
+                this.event = event;
+                this.jobId = jobId;
+            }
+
+            @Override
+            public boolean equals(Object other) {
+                if (this == other) {
+                    return true;
+                }
+                if (other instanceof Event) {
+                    Event otherEvent = (Event) other;
+                    return otherEvent.event == event && otherEvent.jobId == jobId;
+                }
+                return false;
+            }
+
+            @Override
+            public int hashCode() {
+                return event + 31 * jobId;
+            }
+
+            @Override
+            public String toString() {
+                return "Event{" + event + ", " + jobId + "}";
+            }
+        }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
index a92f8ac..7e78e2d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BaseJobSchedulerTest.java
@@ -15,6 +15,8 @@
  */
 package android.jobscheduler.cts;
 
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
 import android.annotation.CallSuper;
 import android.annotation.TargetApi;
 import android.app.Instrumentation;
@@ -217,6 +219,37 @@
         Thread.sleep(2_000);
     }
 
+    void setBatteryState(boolean plugged, int level) throws Exception {
+        if (plugged) {
+            SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
+            final int curLevel = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+                    "dumpsys battery get level").trim());
+            if (curLevel >= level) {
+                // Lower the level so when we set it to the desired level, JobScheduler thinks
+                // the device is charging.
+                SystemUtil.runShellCommand(getInstrumentation(),
+                        "cmd battery set level " + Math.max(1, level - 1));
+            }
+        } else {
+            SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
+        }
+        int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+                "cmd battery set -f level " + level).trim());
+
+        // Wait for the battery update to be processed by job scheduler before proceeding.
+        waitUntil("JobScheduler didn't update charging status to " + plugged, 15 /* seconds */,
+                () -> {
+                    int curSeq;
+                    boolean curCharging;
+                    curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
+                            "cmd jobscheduler get-battery-seq").trim());
+                    curCharging = Boolean.parseBoolean(
+                            SystemUtil.runShellCommand(getInstrumentation(),
+                                    "cmd jobscheduler get-battery-charging").trim());
+                    return curSeq >= seq && curCharging == plugged;
+                });
+    }
+
     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
     void runSatisfiedJob(int jobId) throws Exception {
         SystemUtil.runShellCommand(getInstrumentation(),
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index 3974c38..f6ff23f 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -22,7 +22,6 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.BatteryManager;
-import android.os.SystemClock;
 import android.provider.Settings;
 import android.util.Log;
 
@@ -92,34 +91,6 @@
         return present;
     }
 
-    void setBatteryState(boolean plugged, int level) throws Exception {
-        if (plugged) {
-            SystemUtil.runShellCommand(getInstrumentation(), "cmd battery set ac 1");
-        } else {
-            SystemUtil.runShellCommand(getInstrumentation(), "cmd battery unplug");
-        }
-        int seq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
-                "cmd battery set -f level " + level).trim());
-        long startTime = SystemClock.elapsedRealtime();
-
-        // Wait for the battery update to be processed by job scheduler before proceeding.
-        int curSeq;
-        boolean curCharging;
-        do {
-            Thread.sleep(50);
-            curSeq = Integer.parseInt(SystemUtil.runShellCommand(getInstrumentation(),
-                    "cmd jobscheduler get-battery-seq").trim());
-            curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
-                    "cmd jobscheduler get-battery-charging").trim());
-            if (curSeq >= seq && curCharging == plugged) {
-                return;
-            }
-        } while ((SystemClock.elapsedRealtime() - startTime) < 5000);
-
-        fail("Timed out waiting for job scheduler: expected seq=" + seq + ", cur=" + curSeq
-                + ", expected plugged=" + plugged + " curCharging=" + curCharging);
-    }
-
     void verifyChargingState(boolean charging) throws Exception {
         boolean curCharging = Boolean.parseBoolean(SystemUtil.runShellCommand(getInstrumentation(),
                 "cmd jobscheduler get-battery-charging").trim());
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index b440d83..9b0af4d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -937,16 +937,14 @@
     /**
      * Ensure WiFi is enabled, and block until we've verified that we are in fact connected.
      */
-    private void connectToWifi()
-            throws InterruptedException {
+    private void connectToWifi() throws Exception {
         setWifiState(true, mCm, mWifiManager);
     }
 
     /**
      * Ensure WiFi is disabled, and block until we've verified that we are in fact disconnected.
      */
-    private void disconnectFromWifi()
-            throws InterruptedException {
+    private void disconnectFromWifi() throws Exception {
         setWifiState(false, mCm, mWifiManager);
     }
 
@@ -964,7 +962,7 @@
      * Taken from {@link android.net.http.cts.ApacheHttpClientTest}.
      */
     static void setWifiState(final boolean enable,
-            final ConnectivityManager cm, final WifiManager wm) throws InterruptedException {
+            final ConnectivityManager cm, final WifiManager wm) throws Exception {
         if (enable != isWiFiConnected(cm, wm)) {
             NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
             NetworkCapabilities nc = new NetworkCapabilities.Builder()
@@ -975,6 +973,7 @@
 
             if (enable) {
                 SystemUtil.runShellCommand("svc wifi enable");
+                waitUntil("Failed to enable Wifi", 30 /* seconds */, () -> wm.isWifiEnabled());
                 //noinspection deprecation
                 SystemUtil.runWithShellPermissionIdentity(wm::reconnect,
                         android.Manifest.permission.NETWORK_SETTINGS);
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index eece252..2c54508 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -164,6 +164,7 @@
         // Test all allowed constraints.
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setExpedited(true)
+                .setPriority(JobInfo.PRIORITY_HIGH)
                 .setPersisted(true)
                 .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                 .setRequiresStorageNotLow(true)
@@ -172,6 +173,14 @@
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
+        // Confirm default priority for EJs.
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setExpedited(true)
+                .build();
+        assertEquals(JobInfo.PRIORITY_MAX, ji.getPriority());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
         // Test disallowed constraints.
         final String failureMessage =
                 "Successfully built an expedited JobInfo object with disallowed constraints";
@@ -190,6 +199,14 @@
         assertBuildFails(failureMessage,
                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                         .setExpedited(true)
+                        .setPriority(JobInfo.PRIORITY_LOW));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
+                        .setPriority(JobInfo.PRIORITY_DEFAULT));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setExpedited(true)
                         .setImportantWhileForeground(true));
         assertBuildFails(failureMessage,
                 new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -216,6 +233,7 @@
                         .addTriggerContentUri(tcu));
     }
 
+    @SuppressWarnings("deprecation")
     public void testImportantWhileForeground() {
         // Assert the default value is false
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
@@ -228,6 +246,7 @@
                 .setImportantWhileForeground(true)
                 .build();
         assertTrue(ji.isImportantWhileForeground());
+        assertEquals(JobInfo.PRIORITY_HIGH, ji.getPriority());
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
 
@@ -239,6 +258,55 @@
         mJobScheduler.schedule(ji);
     }
 
+    public void testMinimumChunkSizeBytes() {
+        assertBuildFails(
+                "Successfully built a JobInfo specifying minimum chunk bytes without"
+                        + " requesting network",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setMinimumNetworkChunkBytes(500));
+        try {
+            assertBuildFails(
+                    "Successfully built a JobInfo specifying minimum chunk bytes a negative value",
+                    new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                            .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                            .setMinimumNetworkChunkBytes(-500));
+        } catch (IllegalArgumentException expected) {
+            // Success. setMinimumNetworkChunkBytes() should throw the exception.
+        }
+
+        assertBuildFails(
+                "Successfully built a JobInfo with a higher minimum chunk size than total"
+                        + " transfer size",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setMinimumNetworkChunkBytes(500)
+                        .setEstimatedNetworkBytes(5, 5));
+        assertBuildFails(
+                "Successfully built a JobInfo with a higher minimum chunk size than total"
+                        + " transfer size",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setMinimumNetworkChunkBytes(500)
+                        .setEstimatedNetworkBytes(JobInfo.NETWORK_BYTES_UNKNOWN, 5));
+        assertBuildFails(
+                "Successfully built a JobInfo with a higher minimum chunk size than total"
+                        + " transfer size",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                        .setMinimumNetworkChunkBytes(500)
+                        .setEstimatedNetworkBytes(5, JobInfo.NETWORK_BYTES_UNKNOWN));
+
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+                .setMinimumNetworkChunkBytes(500)
+                .setEstimatedNetworkBytes(
+                        JobInfo.NETWORK_BYTES_UNKNOWN, JobInfo.NETWORK_BYTES_UNKNOWN)
+                .build();
+        assertEquals(500, ji.getMinimumNetworkChunkBytes());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+    }
+
     public void testMinimumLatency() {
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setMinimumLatency(1337)
@@ -320,6 +388,85 @@
         assertFalse(ji.isPrefetch());
         // Confirm JobScheduler accepts the JobInfo object.
         mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setMinimumLatency(60_000L)
+                .setPrefetch(true)
+                .build();
+        assertTrue(ji.isPrefetch());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        // CTS naturally targets latest SDK version. Compat change should be enabled by default.
+        assertBuildFails("Modern prefetch jobs can't have a deadline",
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setMinimumLatency(60_000L)
+                        .setOverrideDeadline(600_000L)
+                        .setPrefetch(true));
+    }
+
+    public void testPriority() {
+        // Assert the default value is DEFAULT
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .build();
+        assertEquals(JobInfo.PRIORITY_DEFAULT, ji.getPriority());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPriority(JobInfo.PRIORITY_LOW)
+                .build();
+        assertEquals(JobInfo.PRIORITY_LOW, ji.getPriority());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setPriority(JobInfo.PRIORITY_MIN)
+                .build();
+        assertEquals(JobInfo.PRIORITY_MIN, ji.getPriority());
+        // Confirm JobScheduler accepts the JobInfo object.
+        mJobScheduler.schedule(ji);
+
+        // Attempt an invalid number
+        try {
+            // It's over 9000!!!
+            new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(9001).build();
+            fail("Successfully built a job with an invalid priority level");
+        } catch (Exception e) {
+            // Success
+        }
+        try {
+            new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(-1).build();
+            fail("Successfully built a job with an invalid priority level");
+        } catch (Exception e) {
+            // Success
+        }
+        try {
+            new JobInfo.Builder(JOB_ID, kJobServiceComponent).setPriority(123).build();
+            fail("Successfully built a job with an invalid priority level");
+        } catch (Exception e) {
+            // Success
+        }
+
+        // Test other invalid configurations.
+        final String failureMessage =
+                "Successfully built a JobInfo object with disallowed priority configurations";
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPriority(JobInfo.PRIORITY_MAX));
+        //noinspection deprecation
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPriority(JobInfo.PRIORITY_LOW)
+                        .setImportantWhileForeground(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPriority(JobInfo.PRIORITY_HIGH)
+                        .setPrefetch(true));
+        assertBuildFails(failureMessage,
+                new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                        .setPriority(JobInfo.PRIORITY_HIGH)
+                        .setPeriodic(JobInfo.getMinPeriodMillis()));
     }
 
     public void testRequiredNetwork() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
index 05d78de..830486e 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobSchedulingTest.java
@@ -19,10 +19,15 @@
 import android.annotation.TargetApi;
 import android.app.job.JobInfo;
 import android.app.job.JobScheduler;
+import android.jobscheduler.MockJobService.TestEnvironment;
+import android.jobscheduler.MockJobService.TestEnvironment.Event;
 import android.provider.DeviceConfig;
 
+import com.android.compatibility.common.util.BatteryUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.util.List;
+
 /**
  * Tests related to scheduling jobs.
  */
@@ -30,11 +35,15 @@
 public class JobSchedulingTest extends BaseJobSchedulerTest {
     private static final int MIN_SCHEDULE_QUOTA = 250;
     private static final int JOB_ID = JobSchedulingTest.class.hashCode();
+    // The maximum number of jobs that can run concurrently.
+    private static final int MAX_JOB_CONTEXTS_COUNT = 16;
 
     @Override
     public void tearDown() throws Exception {
         mJobScheduler.cancel(JOB_ID);
         SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler reset-schedule-quota");
+        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery off");
+        BatteryUtils.runDumpsysBatteryReset();
 
         // The super method should be called at the end.
         super.tearDown();
@@ -127,4 +136,45 @@
             assertEquals(JobScheduler.RESULT_SUCCESS, mJobScheduler.schedule(jobInfo));
         }
     }
+
+    public void testHigherPriorityJobRunsFirst() throws Exception {
+        SystemUtil.runShellCommand(getInstrumentation(), "cmd jobscheduler monitor-battery on");
+
+        setBatteryState(false, 75);
+        final int higherPriorityJobId = JOB_ID;
+        final int numMinPriorityJobs = 2 * MAX_JOB_CONTEXTS_COUNT;
+        kTestEnvironment.setExpectedExecutions(1 + numMinPriorityJobs);
+        for (int i = 0; i < numMinPriorityJobs; ++i) {
+            JobInfo job = new JobInfo.Builder(higherPriorityJobId + 1 + i, kJobServiceComponent)
+                    .setPriority(JobInfo.PRIORITY_MIN)
+                    .setRequiresCharging(true)
+                    .build();
+            mJobScheduler.schedule(job);
+        }
+        // Schedule the higher priority job last since the default sorting is by enqueue time.
+        JobInfo jobMax = new JobInfo.Builder(higherPriorityJobId, kJobServiceComponent)
+                .setPriority(JobInfo.PRIORITY_DEFAULT)
+                .setRequiresCharging(true)
+                .build();
+        mJobScheduler.schedule(jobMax);
+
+        setBatteryState(true, 100);
+        kTestEnvironment.awaitExecution();
+
+        Event jobHigherExecution = new Event(TestEnvironment.Event.EVENT_START_JOB,
+                higherPriorityJobId);
+        List<Event> executedEvents = kTestEnvironment.getExecutedEvents();
+        boolean higherExecutedFirst = false;
+        // Due to racing, we can't just check the very first item in the array. We can however
+        // make sure it was in the first set of jobs to run.
+        for (int i = 0; i < executedEvents.size() && i < MAX_JOB_CONTEXTS_COUNT; ++i) {
+            if (executedEvents.get(i).equals(jobHigherExecution)) {
+                higherExecutedFirst = true;
+                break;
+            }
+        }
+        assertTrue(
+                "Higher priority job (" + higherPriorityJobId + ") didn't run in first batch: "
+                        + executedEvents, higherExecutedFirst);
+    }
 }
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 8ab63ca..de4bc7c 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -23,7 +23,6 @@
 import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
-import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
 
 import static com.android.compatibility.common.util.TestUtils.waitUntil;
 
@@ -133,7 +132,6 @@
             Log.d(TAG, "Received action " + intent.getAction());
             switch (intent.getAction()) {
                 case ACTION_DEVICE_IDLE_MODE_CHANGED:
-                case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
                     synchronized (JobThrottlingTest.this) {
                         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
                         Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
@@ -159,7 +157,6 @@
         mTestAppInterface = new TestAppInterface(mContext, mTestJobId);
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
-        intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
         mContext.registerReceiver(mReceiver, intentFilter);
         assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
         makeTestPackageIdle();
@@ -197,7 +194,7 @@
         if (mAutomotiveDevice || mLeanbackOnly) {
             setScreenState(true);
             // TODO(b/159176758): make sure that initial power supply is on.
-            BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+            setChargingState(true);
         }
 
         // Kill as many things in the background as possible so we avoid LMK interfering with the
@@ -354,31 +351,97 @@
                 mTestAppInterface.getLastParams().getStopReason());
     }
 
-    @RequiresDevice // Emulators don't always have access to wifi/network
     @Test
-    public void testBackgroundConnectivityJobsThrottled() throws Exception {
-        if (!mHasWifi) {
-            Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
-            return;
-        }
-        ensureSavedWifiNetwork(mWifiManager);
-        setAirplaneMode(false);
-        setWifiState(true, mCm, mWifiManager);
-        assumeTrue("device idle not enabled", mDeviceIdleEnabled);
-        mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
+    public void testBackgroundRegJobsThermal() throws Exception {
+        mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
         runJob();
         assertTrue("Job did not start after scheduling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_LIGHT);
+        assertFalse("Job stopped below thermal throttling threshold",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
         assertTrue("Job did not stop on thermal throttling",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
-        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
+        final long jobStopTime = System.currentTimeMillis();
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+        runJob();
+        assertFalse("Job started above thermal throttling threshold",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
+        runJob();
+        assertFalse("Job started above thermal throttling threshold",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
+                - (System.currentTimeMillis() - jobStopTime));
         ThermalUtils.overrideThermalNotThrottling();
         runJob();
         assertTrue("Job did not start back from throttling",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
     }
 
+    @Test
+    public void testBackgroundEJsThermal() throws Exception {
+        mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, true);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
+        assertFalse("Job stopped below thermal throttling threshold",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
+        assertTrue("Job did not stop on thermal throttling",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+        final long jobStopTime = System.currentTimeMillis();
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+        runJob();
+        assertFalse("Job started above thermal throttling threshold",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
+        runJob();
+        assertFalse("Job started above thermal throttling threshold",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
+                - (System.currentTimeMillis() - jobStopTime));
+        ThermalUtils.overrideThermalNotThrottling();
+        runJob();
+        assertTrue("Job did not start back from throttling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+    }
+
+    @Test
+    public void testForegroundJobsThermal() throws Exception {
+        // Turn the screen on to ensure the app gets into the TOP state.
+        setScreenState(true);
+        mTestAppInterface.startAndKeepTestActivity(true);
+        mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
+        runJob();
+        assertTrue("Job did not start after scheduling",
+                mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
+        assertFalse("Job stopped below thermal throttling threshold",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
+        assertFalse("Job stopped despite being TOP app",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+
+        ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
+        assertFalse("Job stopped despite being TOP app",
+                mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
+    }
+
     /** Tests that apps in the RESTRICTED bucket still get their one parole session per day. */
     @Test
     public void testJobsInRestrictedBucket_ParoleSession() throws Exception {
@@ -393,7 +456,7 @@
 
         setScreenState(true);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         sendScheduleJobBroadcast(false);
@@ -422,7 +485,7 @@
         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "1");
 
         setScreenState(true);
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         BatteryUtils.runDumpsysBatterySetLevel(100);
 
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
@@ -454,7 +517,7 @@
 
         setScreenState(true);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         sendScheduleJobBroadcast(false);
@@ -466,7 +529,7 @@
         assertFalse("New job started in RESTRICTED bucket after parole used",
                 mTestAppInterface.awaitJobStart(3_000));
 
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         BatteryUtils.runDumpsysBatterySetLevel(100);
         assertFalse("New job started in RESTRICTED bucket after parole when charging but not idle",
                 mTestAppInterface.awaitJobStart(3_000));
@@ -478,6 +541,7 @@
 
         // Make sure job can be stopped and started again when charging + idle
         sendScheduleJobBroadcast(false);
+        runJob();
         assertTrue("Job didn't restart in RESTRICTED bucket when charging + idle",
                 mTestAppInterface.awaitJobStart(3_000));
     }
@@ -497,7 +561,7 @@
         setAirplaneMode(true);
         setScreenState(true);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
@@ -506,7 +570,7 @@
         // Slowly add back required bucket constraints.
 
         // Battery charging and high.
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
         BatteryUtils.runDumpsysBatterySetLevel(100);
         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
@@ -538,7 +602,7 @@
         setAirplaneMode(true);
         setScreenState(true);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.RESTRICTED);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
@@ -548,7 +612,7 @@
         // Slowly add back required bucket constraints.
 
         // Battery charging and high.
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         runJob();
         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
         BatteryUtils.runDumpsysBatterySetLevel(100);
@@ -578,7 +642,7 @@
         assumeFalse("not testable in automotive device", mAutomotiveDevice);
         assumeFalse("not testable in leanback device", mLeanbackOnly);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.NEVER);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
         sendScheduleJobBroadcast(false);
@@ -590,7 +654,7 @@
         assumeFalse("not testable in automotive device", mAutomotiveDevice);
         assumeFalse("not testable in leanback device", mLeanbackOnly);
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.NEVER);
         tempWhitelistTestApp(6_000);
         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
@@ -606,7 +670,7 @@
 
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(false);
         sendScheduleJobBroadcast(false);
         assertTrue("New job failed to start with battery saver OFF",
@@ -620,7 +684,7 @@
 
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(true);
         sendScheduleJobBroadcast(false);
         assertFalse("New job started with battery saver ON",
@@ -634,7 +698,7 @@
 
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(true);
         tempWhitelistTestApp(6_000);
         sendScheduleJobBroadcast(false);
@@ -650,7 +714,7 @@
         BatteryUtils.assumeBatterySaverFeature();
 
         // Enable battery saver, and schedule a job. It shouldn't run.
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(true);
         sendScheduleJobBroadcast(false);
         assertFalse("New job started with battery saver ON",
@@ -669,7 +733,7 @@
 
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(true);
         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
         assertTrue("New expedited job failed to start with battery saver ON",
@@ -683,7 +747,7 @@
 
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(false);
         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
         assertTrue("New expedited job failed to start with battery saver ON",
@@ -756,7 +820,7 @@
         // Intentionally set a value below 1 minute to ensure the range checks work.
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(47_000L));
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(true);
         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
         runJob();
@@ -788,7 +852,7 @@
         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         toggleDozeState(true);
         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
         runJob();
@@ -846,7 +910,7 @@
         // Intentionally set a value below 1 minute to ensure the range checks work.
         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(0L));
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(false);
         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
         runJob();
@@ -883,7 +947,7 @@
         setAirplaneMode(false);
         setWifiState(true, mCm, mWifiManager);
         setWifiMeteredState(false);
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         BatteryUtils.runDumpsysBatterySetLevel(100);
         setScreenState(false);
         triggerJobIdle();
@@ -921,12 +985,12 @@
         runJob();
         assertTrue("New job didn't start in RESTRICTED bucket",
                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         assertTrue("New job didn't stop when device no longer charging",
                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
                 mTestAppInterface.getLastParams().getStopReason());
-        BatteryUtils.runDumpsysBatterySetPluggedIn(true);
+        setChargingState(true);
         BatteryUtils.runDumpsysBatterySetLevel(100);
 
         // Battery not low
@@ -947,7 +1011,7 @@
     public void testRestrictingStopReason_Quota() throws Exception {
         // Reduce allowed time for testing.
         mDeviceConfigStateHelper.set("qc_allowed_time_per_period_ms", "60000");
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         setTestPackageStandbyBucket(Bucket.RARE);
 
         sendScheduleJobBroadcast(false);
@@ -967,7 +1031,7 @@
     public void testRestrictingStopReason_BatterySaver() throws Exception {
         BatteryUtils.assumeBatterySaverFeature();
 
-        BatteryUtils.runDumpsysBatteryUnplug();
+        setChargingState(false);
         BatteryUtils.enableBatterySaver(false);
         sendScheduleJobBroadcast(false);
         runJob();
@@ -1007,6 +1071,7 @@
             toggleDozeState(false);
         }
         mTestAppInterface.cleanup();
+        mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery off");
         BatteryUtils.runDumpsysBatteryReset();
         BatteryUtils.enableBatterySaver(false);
         removeTestAppFromTempWhitelist();
@@ -1132,6 +1197,33 @@
         Thread.sleep(2_000);
     }
 
+    private void setChargingState(boolean isCharging) throws Exception {
+        mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery on");
+
+        final String command;
+        if (isCharging) {
+            mUiDevice.executeShellCommand("cmd battery set ac 1");
+            final int curLevel = Integer.parseInt(
+                    mUiDevice.executeShellCommand("dumpsys battery get level").trim());
+            command = "cmd battery set -f level " + Math.min(100, curLevel + 1);
+        } else {
+            command = "cmd battery unplug -f";
+        }
+        int seq = Integer.parseInt(mUiDevice.executeShellCommand(command).trim());
+
+        // Wait for the battery update to be processed by job scheduler before proceeding.
+        waitUntil("JobScheduler didn't update charging status to " + isCharging, 15 /* seconds */,
+                () -> {
+                    int curSeq;
+                    boolean curCharging;
+                    curSeq = Integer.parseInt(mUiDevice.executeShellCommand(
+                            "cmd jobscheduler get-battery-seq").trim());
+                    curCharging = Boolean.parseBoolean(mUiDevice.executeShellCommand(
+                            "cmd jobscheduler get-battery-charging").trim());
+                    return curSeq >= seq && curCharging == isCharging;
+                });
+    }
+
     /**
      * Trigger job idle (not device idle);
      */
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
index e05969e..457ec1f 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobWorkItemTest.java
@@ -50,6 +50,48 @@
         assertEquals(0, jwi.getDeliveryCount());
     }
 
+    public void testItemWithMinimumChunkBytes() {
+        JobWorkItem jwi = new JobWorkItem(TEST_INTENT, 10, 20, 3);
+
+        assertEquals(TEST_INTENT, jwi.getIntent());
+        assertEquals(10, jwi.getEstimatedNetworkDownloadBytes());
+        assertEquals(20, jwi.getEstimatedNetworkUploadBytes());
+        assertEquals(3, jwi.getMinimumNetworkChunkBytes());
+        // JobWorkItem hasn't been scheduled yet. Delivery count should be 0.
+        assertEquals(0, jwi.getDeliveryCount());
+
+        try {
+            new JobWorkItem(TEST_INTENT, 10, 20, -3);
+            fail("Successfully created JobWorkItem with negative minimum chunk value");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+        try {
+            new JobWorkItem(TEST_INTENT, 10, 20, 0);
+            fail("Successfully created JobWorkItem with 0 minimum chunk value");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+        try {
+            new JobWorkItem(TEST_INTENT, 10, 20, 50);
+            fail("Successfully created JobWorkItem with minimum chunk value too large");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+        try {
+            new JobWorkItem(TEST_INTENT, JobInfo.NETWORK_BYTES_UNKNOWN, 20, 25);
+            fail("Successfully created JobWorkItem with minimum chunk value too large");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+        try {
+            new JobWorkItem(TEST_INTENT, 10, JobInfo.NETWORK_BYTES_UNKNOWN, 15);
+            fail("Successfully created JobWorkItem with minimum chunk value too large");
+        } catch (IllegalArgumentException expected) {
+            // Success
+        }
+    }
+
     public void testDeliveryCountBumped() throws Exception {
         JobInfo jobInfo = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .setOverrideDeadline(0)
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
index 8dba741..7dc78eb 100644
--- a/tests/MediaProviderTranscode/AndroidTest.xml
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -19,6 +19,11 @@
         <option name="install-arg" value="-g" />
     </target_preparer>
 
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="cts" />
     <option name="test-tag" value="MediaProviderTranscodeTests" />
diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
new file mode 100644
index 0000000..37b049b
--- /dev/null
+++ b/tests/PhotoPicker/Android.bp
@@ -0,0 +1,40 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsPhotoPickerTest",
+    manifest: "AndroidManifest.xml",
+    test_config: "AndroidTest.xml",
+    srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",],
+    compile_multilib: "both",
+    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    sdk_version: "core_current",
+    min_sdk_version: "30",
+    libs: [
+            "android.test.base",
+            "android.test.runner",
+            "framework-mediaprovider.impl",
+            "framework-res",
+            "android_test_stubs_current"],
+    static_libs: [
+            "androidx.test.rules",
+            "cts-install-lib",
+            "androidx.test.uiautomator_uiautomator",
+            "Harrier",
+    ],
+}
diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml
new file mode 100644
index 0000000..ea26ad1
--- /dev/null
+++ b/tests/PhotoPicker/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.photopicker.cts">
+<application android:label="Photo Picker Device Tests">
+    <uses-library android:name="android.test.runner" />
+    <activity android:name="android.photopicker.cts.GetResultActivity" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                 android:targetPackage="android.photopicker.cts"
+                 android:label="Device-only photo picker tests" />
+
+</manifest>
diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml
new file mode 100644
index 0000000..dbd1bc4
--- /dev/null
+++ b/tests/PhotoPicker/AndroidTest.xml
@@ -0,0 +1,45 @@
+<?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="Runs device-only tests for photo picker">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPhotoPickerTest.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+
+    <option name="test-suite-tag" value="cts" />
+    <option name="test-tag" value="PhotoPickerTests" />
+    <!-- Instant apps cannot access external storage -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.photopicker.cts" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+        <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
+    </test>
+    <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS
new file mode 100644
index 0000000..79add9e
--- /dev/null
+++ b/tests/PhotoPicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 95221
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING
new file mode 100644
index 0000000..f48e90c
--- /dev/null
+++ b/tests/PhotoPicker/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPhotoPickerTest"
+    }
+  ]
+}
diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 0000000..d264196
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/testvideo_meta.mp4 b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
new file mode 100644
index 0000000..9b38f0e
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/testvideo_meta.mp4
Binary files differ
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
new file mode 100644
index 0000000..35e7830
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.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.photopicker.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    private static LinkedBlockingQueue<Result> sResult;
+
+    public static class Result {
+        public final int requestCode;
+        public final int resultCode;
+        public final Intent data;
+
+        public Result(int requestCode, int resultCode, Intent data) {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.data = data;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        try {
+            sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void clearResult() {
+        sResult = new LinkedBlockingQueue<>();
+    }
+
+    public Result getResult() {
+        final Result result;
+        try {
+            result = sResult.poll(40, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalStateException("Activity didn't receive a Result in 40 seconds");
+        }
+        return result;
+    }
+
+    public Result getResult(long timeout, TimeUnit unit) {
+        try {
+            return sResult.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
new file mode 100644
index 0000000..49a1acb
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.photopicker.cts;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import org.junit.Before;
+
+/**
+ * Photo Picker Base class for Photo Picker tests. This includes common setup methods
+ * required for all Photo Picker tests.
+ */
+public class PhotoPickerBaseTest {
+    public static int REQUEST_CODE = 42;
+
+    protected GetResultActivity mActivity;
+    protected Context mContext;
+    protected UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+
+        enablePhotoPickerFlag(inst);
+
+        mContext = inst.getContext();
+        final Intent intent = new Intent(mContext, GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // Wake up the device and dismiss the keyguard before the test starts
+        mDevice = UiDevice.getInstance(inst);
+        mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mDevice.executeShellCommand("wm dismiss-keyguard");
+
+        mActivity = (GetResultActivity) inst.startActivitySync(intent);
+        // Wait for the UI Thread to become idle.
+        inst.waitForIdleSync();
+        mActivity.clearResult();
+        mDevice.waitForIdle();
+    }
+
+    private void enablePhotoPickerFlag(Instrumentation inst) {
+        inst.getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.WRITE_DEVICE_CONFIG);
+        DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_STORAGE_NATIVE_BOOT,
+                "picker_intent_enabled" /* name */,
+                "true" /* value */,
+                false /* makeDefault */);
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
new file mode 100644
index 0000000..53810f7
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.UiObject;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for cross profile interaction flows.
+ */
+@RunWith(BedsteadJUnit4.class)
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest {
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext.getUserId());
+        }
+        mActivity.finish();
+    }
+
+    @Test
+    @RequireRunOnWorkProfile
+    public void testWorkApp_canAccessPersonalProfileContents() throws Exception {
+        final int imageCount = 2;
+        createImages(imageCount, sDeviceState.primaryUser().id(), mUriList);
+
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // Click the profile button to change to personal profile
+        final UiObject profileButton = findProfileButton();
+        profileButton.click();
+        mDevice.waitForIdle();
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(imageCount);
+        for (int i = 0; i < count; i++) {
+            Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, sDeviceState.primaryUser().id());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    @EnsureHasWorkProfile
+    public void testPersonalApp_canAccessWorkProfileContents() throws Exception {
+        final int imageCount = 2;
+        createImages(imageCount, sDeviceState.workProfile().id(), mUriList);
+
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // Click the profile button to change to work profile
+        final UiObject profileButton = findProfileButton();
+        profileButton.click();
+        mDevice.waitForIdle();
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(imageCount);
+        for (int i = 0; i < count; i++) {
+            Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, sDeviceState.workProfile().id());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
new file mode 100644
index 0000000..9b5904c
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -0,0 +1,366 @@
+/*
+ * 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for common flows.
+ */
+@RunWith(AndroidJUnit4.class)
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
+public class PhotoPickerTest extends PhotoPickerBaseTest {
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext.getUserId());
+        }
+        mActivity.finish();
+    }
+
+    @Test
+    public void testSingleSelect() throws Exception {
+        final int itemCount = 1;
+        createImages(itemCount, mContext.getUserId(), mUriList);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final UiObject item = findItemList(itemCount).get(0);
+        item.click();
+        mDevice.waitForIdle();
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testSingleSelectWithPreview() throws Exception {
+        final int itemCount = 1;
+        createImages(itemCount, mContext.getUserId(), mUriList);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final UiObject item = findItemList(itemCount).get(0);
+        item.longClick();
+        mDevice.waitForIdle();
+
+        final UiObject addButton = findPreviewAddOrSelectButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testMultiSelect_invalidParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        // TODO(b/205291616): Replace 101 with MediaStore.getPickImagesMaxLimit() + 1
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 101);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_invalidNegativeParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+        final int maxCount = 2;
+        final int imageCount = maxCount + 1;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select maxCount + 1 item
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+                "Select up to 2 items"));
+        assertWithMessage("Timed out while waiting for snackbar to appear").that(
+                snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        assertWithMessage("Timed out waiting for snackbar to disappear").that(
+                snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(maxCount);
+    }
+
+    @Test
+    public void testDoesNotRespectExtraAllowMultiple() throws Exception {
+        final int imageCount = 2;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select 1 item
+        final UiObject item = itemList.get(0);
+        item.click();
+        mDevice.waitForIdle();
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testMultiSelect() throws Exception {
+        final int imageCount = 4;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    public void testMultiSelect_longPress() throws Exception {
+        final int videoCount = 3;
+        createVideos(videoCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+        intent.setType("video/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(videoCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(videoCount);
+
+        // Select one item from Photo grid
+        itemList.get(0).click();
+        mDevice.waitForIdle();
+
+        UiObject item = itemList.get(1);
+        item.longClick();
+        mDevice.waitForIdle();
+
+        // Select the item from Preview
+        final UiObject selectButton = findPreviewAddOrSelectButton();
+        selectButton.click();
+        mDevice.waitForIdle();
+
+        mDevice.pressBack();
+
+        // Select one more item from Photo grid
+        itemList.get(2).click();
+        mDevice.waitForIdle();
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        // Verify that all 3 items are returned
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(3);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    public void testMultiSelect_preview() throws Exception {
+        final int imageCount = 4;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject viewSelectedButton = findViewSelectedButton();
+        viewSelectedButton.click();
+        mDevice.waitForIdle();
+
+        // Swipe left three times
+        swipeLeft();
+        mDevice.waitForIdle();
+        swipeLeft();
+        mDevice.waitForIdle();
+        swipeLeft();
+        mDevice.waitForIdle();
+
+        // Deselect one item
+        final UiObject selectCheckButton = findPreviewSelectCheckButton();
+        selectCheckButton.click();
+        mDevice.waitForIdle();
+
+        final UiObject addButton = findPreviewAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount - 1);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    public void testMimeTypeFilter() throws Exception {
+        final int videoCount = 2;
+        createVideos(videoCount, mContext.getUserId(), mUriList);
+        final int imageCount = 1;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final String mimeType = "video/dng";
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        // TODO(b/205291616): Replace 100 with MediaStore.getPickImagesMaxLimit()
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, 100);
+        intent.setType(mimeType);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // find all items
+        final List<UiObject> itemList = findItemList(-1);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isAtLeast(videoCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertRedactedReadOnlyAccess(uri);
+            assertMimeType(uri, mimeType);
+        }
+    }
+
+    private static UiObject findViewSelectedButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/button_view_selected"));
+    }
+
+    private static UiObject findPreviewSelectCheckButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_select_check_button"));
+    }
+
+    private void swipeLeft() {
+        final int width = mDevice.getDisplayWidth();
+        final int height = mDevice.getDisplayHeight();
+        mDevice.swipe(width / 2, height / 2, width / 4, height / 2, 10);
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
new file mode 100644
index 0000000..4a8f823
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -0,0 +1,151 @@
+/*
+ * 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.photopicker.cts.util;
+
+import static android.os.SystemProperties.getBoolean;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE;
+import static android.provider.MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO;
+import static android.provider.MediaStore.PickerMediaColumns;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Arrays;
+
+/**
+ * Photo Picker Utility methods for test assertions.
+ */
+public class PhotoPickerAssertionsUtils {
+    private static final String TAG = "PhotoPickerTestAssertions";
+
+    public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
+        // content://media/picker/<user-id>/<media-id>
+        final int userId = Integer.parseInt(uri.getPathSegments().get(1));
+        assertThat(userId).isEqualTo(expectedUserId);
+
+        final String auth = uri.getPathSegments().get(0);
+        assertThat(auth).isEqualTo("picker");
+    }
+
+    public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String resultMimeType = context.getContentResolver().getType(uri);
+        assertThat(resultMimeType).isEqualTo(expectedMimeType);
+    }
+
+    public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
+        assertThat(uri).isNotNull();
+        final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final ContentResolver resolver = context.getContentResolver();
+        try (Cursor c = resolver.query(uri, projection, null, null)) {
+            assertThat(c).isNotNull();
+            assertThat(c.moveToFirst()).isTrue();
+
+            if (getBoolean("sys.photopicker.pickerdb.enabled", true)) {
+                final String mimeType = c.getString(c.getColumnIndex(
+                                PickerMediaColumns.MIME_TYPE));
+                if (mimeType.startsWith("image")) {
+                    assertImageRedactedReadOnlyAccess(uri, resolver);
+                } else if (mimeType.startsWith("video")) {
+                    assertVideoRedactedReadOnlyAccess(uri, resolver);
+                } else {
+                    fail("The mime type is not as expected: " + mimeType);
+                }
+            } else {
+                final int mediaType = c.getInt(1);
+                switch (mediaType) {
+                    case MEDIA_TYPE_IMAGE:
+                        assertImageRedactedReadOnlyAccess(uri, resolver);
+                        break;
+                    case MEDIA_TYPE_VIDEO:
+                        assertVideoRedactedReadOnlyAccess(uri, resolver);
+                        break;
+                    default:
+                        fail("The media type is not as expected: " + mediaType);
+                }
+            }
+        }
+    }
+
+    private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+            throws Exception {
+        // The location is redacted
+        try (InputStream in = resolver.openInputStream(uri);
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            FileUtils.copy(in, out);
+            byte[] bytes = out.toByteArray();
+            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+            String xmp = new String(xmpBytes);
+            assertWithMessage("Failed to redact XMP longitude")
+                    .that(xmp.contains("10,41.751000E")).isFalse();
+            assertWithMessage("Failed to redact XMP latitude")
+                    .that(xmp.contains("53,50.070500N")).isFalse();
+            assertWithMessage("Redacted non-location XMP")
+                    .that(xmp.contains("13166/7763")).isTrue();
+        }
+
+        // assert no write access
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+            fail("Does not grant write access to uri " + uri.toString());
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+
+    private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+            throws Exception {
+        // The location is redacted
+        try (InputStream is = resolver.openInputStream(uri)) {
+            final ExifInterface exif = new ExifInterface(is);
+            final float[] latLong = new float[2];
+            exif.getLatLong(latLong);
+            assertWithMessage("Failed to redact latitude")
+                    .that(latLong[0]).isWithin(0.001f).of(0);
+            assertWithMessage("Failed to redact longitude")
+                    .that(latLong[1]).isWithin(0.001f).of(0);
+
+            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+            assertWithMessage("Failed to redact XMP longitude")
+                    .that(xmp.contains("10,41.751000E")).isFalse();
+            assertWithMessage("Failed to redact XMP latitude")
+                    .that(xmp.contains("53,50.070500N")).isFalse();
+            assertWithMessage("Redacted non-location XMP")
+                    .that(xmp.contains("LensDefaults")).isTrue();
+        }
+
+        // assert no write access
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+            fail("Does not grant write access to uri " + uri.toString());
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
new file mode 100644
index 0000000..37a44f9
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -0,0 +1,116 @@
+/*
+ * 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.photopicker.cts.util;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.photopicker.cts.R;
+import android.provider.MediaStore;
+import android.provider.cts.media.MediaStoreUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for media files creation and deletion.
+ */
+public class PhotoPickerFilesUtils {
+    public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
+
+    public static void createImages(int count, int userId, List<Uri> uriList)
+            throws Exception {
+        for (int i = 0; i < count; i++) {
+            final Uri uri = createImage(userId);
+            uriList.add(uri);
+            clearMediaOwner(uri, userId);
+        }
+    }
+
+    public static void createVideos(int count, int userId, List<Uri> uriList)
+            throws Exception {
+        for (int i = 0; i < count; i++) {
+            final Uri uri = createVideo(userId);
+            uriList.add(uri);
+            clearMediaOwner(uri, userId);
+        }
+    }
+
+    public static void deleteMedia(Uri uri, int userId) throws Exception {
+        final String cmd = String.format("content delete --uri %s --user %d", uri, userId);
+        ShellUtils.runShellCommand(cmd);
+    }
+
+    private static void clearMediaOwner(Uri uri, int userId) throws Exception {
+        final String cmd = String.format(
+                "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId);
+        ShellUtils.runShellCommand(cmd);
+    }
+
+    private static Uri createVideo(int userId) throws Exception {
+        final Uri uri = stageMedia(R.raw.testvideo_meta,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+        return uri;
+    }
+
+    private static Uri createImage(int userId) throws Exception {
+        final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId);
+        return uri;
+    }
+
+    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
+            Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity(
+                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            final Context context = InstrumentationRegistry.getTargetContext();
+            final Context userContext = userId == context.getUserId() ? context :
+                    context.createPackageContextAsUser("android", /* flags= */ 0,
+                            UserHandle.of(userId));
+            return stageMedia(resId, collectionUri, mimeType, userContext);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context)
+            throws IOException {
+        final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
+        final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
+                collectionUri, displayName, mimeType);
+        final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+        try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context,
+                pendingUri)) {
+            try (InputStream source = context.getResources().openRawResource(resId);
+                 OutputStream target = session.openOutputStream()) {
+                FileUtils.copy(source, target);
+            }
+            return session.publish();
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
new file mode 100644
index 0000000..d20dcd6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -0,0 +1,95 @@
+/*
+ * 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.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.format.DateUtils;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for finding UI elements.
+ */
+public class PhotoPickerUiUtils {
+    public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
+
+    private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+    public static final String REGEX_PACKAGE_NAME =
+            "com(.google)?.android.providers.media(.module)?";
+
+    /**
+     * Get the list of items from the photo grid list.
+     * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the
+     *                  item list that its size is not greater than the itemCount.
+     * @throws Exception
+     */
+    public static List<UiObject> findItemList(int itemCount) throws Exception {
+        final List<UiObject> itemList = new ArrayList<>();
+        final UiSelector gridList = new UiSelector().className(
+                "androidx.recyclerview.widget.RecyclerView").resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview");
+
+        // Wait for the first item to appear
+        assertWithMessage("Timed out while waiting for first item to appear")
+                .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT))
+                .isTrue();
+
+        final UiSelector itemSelector = new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/icon_thumbnail");
+        final UiScrollable grid = new UiScrollable(gridList);
+        final int childCount = grid.getChildCount();
+        final int count = itemCount == -1 ? childCount : itemCount;
+
+        for (int i = 0; i < childCount; i++) {
+            final UiObject item = grid.getChildByInstance(itemSelector, i);
+            if (item.exists()) {
+                itemList.add(item);
+            }
+            if (itemList.size() == count) {
+                break;
+            }
+        }
+        return itemList;
+    }
+
+    public static UiObject findPreviewAddButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_add_button"));
+    }
+
+    public static UiObject findPreviewAddOrSelectButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_add_or_select_button"));
+    }
+
+    public static UiObject findAddButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/button_add"));
+    }
+
+    public static UiObject findProfileButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/profile_button"));
+    }
+}
diff --git a/tests/ProcessTest/Android.bp b/tests/ProcessTest/Android.bp
deleted file mode 100644
index a0c7de5..0000000
--- a/tests/ProcessTest/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// TODO: should it be android_helper_test_app?
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "ProcessTests",
-    defaults: ["cts_defaults"],
-    aaptflags: [
-        "-c",
-        "xx_YY",
-        "-c",
-        "cs",
-    ],
-    srcs: ["**/*.java"],
-    static_libs: ["junit"],
-    libs: ["android.test.base"],
-    dex_preopt: {
-        enabled: false,
-    },
-    optimize: {
-        enabled: false,
-    },
-    sdk_version: "current",
-}
diff --git a/tests/ProcessTest/AndroidManifest.xml b/tests/ProcessTest/AndroidManifest.xml
deleted file mode 100644
index f2d7202..0000000
--- a/tests/ProcessTest/AndroidManifest.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.process"
-       android:sharedUserId="com.android.cts.process.uidpid_test">
-
-    <!-- InstrumentationTestRunner for AndroidTests -->
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.process"
-                     android:label="Test process"/>
-    <application>
-
-        <uses-library android:name="android.test.runner" />
-
-        <activity android:name=".activity.SharePidActivity"
-                android:process=":shareProcess"/>
-        <activity android:name=".activity.SharePidSubActivity"
-                android:process=":shareProcess"/>
-        <activity android:name=".activity.NoSharePidActivity"
-                android:process=":noShareProcess"/>
-    </application>
-
-</manifest>
diff --git a/tests/ProcessTest/NoShareUidApp/Android.bp b/tests/ProcessTest/NoShareUidApp/Android.bp
deleted file mode 100644
index e5582de..0000000
--- a/tests/ProcessTest/NoShareUidApp/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "NoShareUidApp",
-    defaults: ["cts_defaults"],
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    optimize: {
-        enabled: false,
-    },
-    dex_preopt: {
-        enabled: false,
-    },
-}
diff --git a/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml b/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml
deleted file mode 100644
index ddc73cf..0000000
--- a/tests/ProcessTest/NoShareUidApp/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.process.noshareuidapp">
-
-</manifest>
diff --git a/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java b/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java
deleted file mode 100644
index e5f0ab4..0000000
--- a/tests/ProcessTest/NoShareUidApp/src/com/android/cts/process/activity/NoSharePidActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class NoSharePidActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/ShareUidApp/Android.bp b/tests/ProcessTest/ShareUidApp/Android.bp
deleted file mode 100644
index 9457f14..0000000
--- a/tests/ProcessTest/ShareUidApp/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2009 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "ShareUidApp",
-    defaults: ["cts_defaults"],
-    srcs: ["**/*.java"],
-    sdk_version: "current",
-    optimize: {
-        enabled: false,
-    },
-    dex_preopt: {
-        enabled: false,
-    },
-}
diff --git a/tests/ProcessTest/ShareUidApp/AndroidManifest.xml b/tests/ProcessTest/ShareUidApp/AndroidManifest.xml
deleted file mode 100644
index 19981f4..0000000
--- a/tests/ProcessTest/ShareUidApp/AndroidManifest.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2009 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-       package="com.android.cts.process.shareuidapp"
-       android:sharedUserId="com.android.cts.process.uidpid_test">
-
-</manifest>
diff --git a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java b/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java
deleted file mode 100644
index 55db78b..0000000
--- a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class SharePidActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java b/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java
deleted file mode 100644
index c06d9fb..0000000
--- a/tests/ProcessTest/ShareUidApp/src/com/android/cts/process/activity/SharePidSubActivity.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.cts.process.activity;
-
-import android.app.Activity;
-
-public class SharePidSubActivity extends Activity {
-
-}
diff --git a/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java b/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java
deleted file mode 100644
index a64a900..0000000
--- a/tests/ProcessTest/src/com/android/cts/process/ProcessTest.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.process;
-
-import java.util.List;
-
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningAppProcessInfo;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.test.AndroidTestCase;
-
-import com.android.cts.process.activity.NoSharePidActivity;
-import com.android.cts.process.activity.SharePidActivity;
-import com.android.cts.process.activity.SharePidSubActivity;
-
-public class ProcessTest extends AndroidTestCase {
-    private final int WAIT_TIME = 2000;
-
-    public void testUid() throws Exception {
-        String enableApp = "com.android.cts.process.shareuidapp";
-        String disableApp = "com.android.cts.process.noshareuidapp";
-        String testApp = mContext.getPackageName();
-        int uid1 = mContext.getPackageManager().getApplicationInfo(enableApp,
-                PackageManager.GET_META_DATA).uid;
-        int uid2 = mContext.getPackageManager().getApplicationInfo(disableApp,
-                PackageManager.GET_META_DATA).uid;
-        int uid3 = mContext.getPackageManager().getApplicationInfo(testApp,
-                PackageManager.GET_META_DATA).uid;
-        assertEquals(uid1, uid3);
-        assertNotSame(uid2, uid3);
-    }
-
-    public void testPid() throws Exception {
-        ActivityManager am = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
-        String shareProcessName = mContext.getPackageName() + ":shareProcess";
-        String noShareProcessName = mContext.getPackageName() + ":noShareProcess";
-        List<RunningAppProcessInfo> list = am.getRunningAppProcesses();
-        assertEquals(-1, getPid(shareProcessName, list));
-        // share pid will use same process
-        Intent sharePidIntent = new Intent();
-        sharePidIntent.setClass(mContext, SharePidActivity.class);
-        sharePidIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(sharePidIntent);
-        Thread.sleep(WAIT_TIME);
-        List<RunningAppProcessInfo> sharelist = am.getRunningAppProcesses();
-        int sharePid = getPid(shareProcessName, sharelist);
-        assertTrue(-1 != sharePid);
-        Intent sharePidStubIntent = new Intent();
-        sharePidStubIntent.setClass(mContext, SharePidSubActivity.class);
-        sharePidStubIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(sharePidStubIntent);
-        Thread.sleep(WAIT_TIME);
-        List<RunningAppProcessInfo> shareStublist = am.getRunningAppProcesses();
-        int shareStubPid = getPid(shareProcessName, shareStublist);
-        assertTrue(-1 != shareStubPid);
-        assertEquals(sharePid, shareStubPid);
-        // no share pid will create a new process
-        Intent noSharePidIntent = new Intent();
-        noSharePidIntent.setClass(mContext, NoSharePidActivity.class);
-        noSharePidIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(noSharePidIntent);
-        Thread.sleep(WAIT_TIME);
-        List<RunningAppProcessInfo> noShareStublist = am.getRunningAppProcesses();
-        int noSharePid = getPid(noShareProcessName, noShareStublist);
-        assertTrue(-1 != noSharePid);
-        assertTrue(sharePid != noSharePid);
-        // kill the process after test
-        android.os.Process.killProcess(noSharePid);
-        android.os.Process.killProcess(sharePid);
-    }
-
-    private int getPid(String processName, List<RunningAppProcessInfo> list) {
-        for (RunningAppProcessInfo rai : list) {
-            if (processName.equals(rai.processName))
-                return rai.pid;
-        }
-        return -1;
-    }
-
-}
diff --git a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
index 067e37b..d0438bd 100644
--- a/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
+++ b/tests/accessibility/common/src/android/accessibility/cts/common/ServiceControlUtils.java
@@ -52,7 +52,7 @@
                 lock.notifyAll();
             }
         };
-        manager.addAccessibilityServicesStateChangeListener(listener, null);
+        manager.addAccessibilityServicesStateChangeListener(listener);
         try {
             waitOn(lock, condition, timeoutMs, conditionName);
         } finally {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
index c083977..f86df5d 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -361,6 +361,27 @@
     }
 
     @Test
+    public void setText_unChanged_doNotReceiveEvent() throws Throwable {
+        sUiAutomation.executeAndWaitForEvent(
+                () -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
+                event -> isExpectedChangeType(event, AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT),
+                DEFAULT_TIMEOUT_MS);
+
+        assertThrows(
+                TimeoutException.class,
+                () ->
+                        sUiAutomation.executeAndWaitForEvent(
+                                () -> {
+                                    sInstrumentation.runOnMainSync(
+                                            () -> {
+                                                mTextView.setText("a");
+                                            });
+                                },
+                                event -> isExpectedSource(event, mTextView),
+                                DEFAULT_TIMEOUT_MS));
+    }
+
+    @Test
     public void setText_textChanged_receivesTextEvent() throws Throwable {
         sUiAutomation.executeAndWaitForEvent(
                 () -> sInstrumentation.runOnMainSync(() -> mTextView.setText("a")),
@@ -610,6 +631,7 @@
         sentEvent.setScrollable(true);
         sentEvent.setAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
         sentEvent.setMovementGranularity(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
+        sentEvent.setDisplayId(Display.DEFAULT_DISPLAY);
 
         AccessibilityRecord record = AccessibilityRecord.obtain();
         AccessibilityRecordTest.fullyPopulateAccessibilityRecord(record);
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
index f1eab6c..1a359dd 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityManagerTest.java
@@ -36,14 +36,18 @@
 import android.os.Handler;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
 import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
+import android.view.accessibility.AccessibilityManager.AudioDescriptionRequestedChangeListener;
 import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.SettingsStateChangerRule;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -77,14 +81,6 @@
             new InstrumentedAccessibilityServiceTestRule<>(
                     SpeakingAndVibratingAccessibilityService.class, false);
 
-    @Rule
-    public final RuleChain mRuleChain = RuleChain
-            .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
-            .around(mVibratingAccessibilityServiceRule)
-            .around(mSpeakingAccessibilityServiceRule)
-            // Inner rule capture failure and dump data before finishing activity and a11y service
-            .around(mDumpOnFailureRule);
-
     private static final Instrumentation sInstrumentation =
             InstrumentationRegistry.getInstrumentation();
 
@@ -102,6 +98,23 @@
 
     public static final String ACCESSIBILITY_INTERACTIVE_UI_TIMEOUT_MS =
             "accessibility_interactive_ui_timeout_ms";
+    private static final String ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT =
+            "enabled_accessibility_audio_description_by_default";
+
+    private final SettingsStateChangerRule mAudioDescriptionSetterRule =
+            new SettingsStateChangerRule(
+                    sInstrumentation.getContext(),
+                    ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
+                    "0");
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mSpeakingAndVibratingAccessibilityServiceRule)
+            .around(mVibratingAccessibilityServiceRule)
+            .around(mSpeakingAccessibilityServiceRule)
+            // Inner rule capture failure and dump data before finishing activity and a11y service
+            .around(mDumpOnFailureRule)
+            .around(mAudioDescriptionSetterRule);
 
     private AccessibilityManager mAccessibilityManager;
 
@@ -141,6 +154,19 @@
     }
 
     @Test
+    public void testAddAndRemoveAudioDescriptionRequestedChangeListener() throws Exception {
+        AudioDescriptionRequestedChangeListener listener = (boolean enabled) -> {
+            // Do nothing.
+        };
+        mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
+                mTargetContext.getMainExecutor(), listener);
+        assertTrue(
+                mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
+        assertFalse(
+                mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(listener));
+    }
+
+    @Test
     public void testIsTouchExplorationEnabled() throws Exception {
         mSpeakingAccessibilityServiceRule.enableService();
         mVibratingAccessibilityServiceRule.enableService();
@@ -153,6 +179,17 @@
     }
 
     @Test
+    public void testRemoveAccessibilityServicesStateChangeListener() throws Exception {
+        AccessibilityServicesStateChangeListener listener = (state) -> {
+            /* do nothing */
+        };
+        mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
+
+        assertTrue(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
+        assertFalse(mAccessibilityManager.removeAccessibilityServicesStateChangeListener(listener));
+    }
+
+    @Test
     public void testGetInstalledAccessibilityServicesList() throws Exception {
         List<AccessibilityServiceInfo> installedServices =
             mAccessibilityManager.getInstalledAccessibilityServiceList();
@@ -318,13 +355,13 @@
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener);
         mSpeakingAccessibilityServiceRule.enableService();
         mVibratingAccessibilityServiceRule.enableService();
-        assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
-                "Touch exploration state listener not called when services enabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+                "Touch exploration state listener called when services enabled");
         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
         InstrumentedAccessibilityService.disableAllServices();
-        assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
-                "Touch exploration state listener not called when services disabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+                "Touch exploration state listener called when services disabled");
         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
@@ -344,19 +381,63 @@
         mAccessibilityManager.addTouchExplorationStateChangeListener(listener, mHandler);
         mSpeakingAccessibilityServiceRule.enableService();
         mVibratingAccessibilityServiceRule.enableService();
-        assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
-                "Touch exploration state listener not called when services enabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+                "Touch exploration state listener called when services enabled");
         assertTrue("Listener told that touch exploration is enabled, but manager says disabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
         InstrumentedAccessibilityService.disableAllServices();
-        assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
-                "Touch exploration state listener not called when services disabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+                "Touch exploration state listener called when services disabled");
         assertFalse("Listener told that touch exploration is disabled, but manager says it enabled",
                 mAccessibilityManager.isTouchExplorationEnabled());
         mAccessibilityManager.removeTouchExplorationStateChangeListener(listener);
     }
 
     @Test
+    public void testAccessibilityServicesStateListenerNoExecutor() {
+        final Object waitObject = new Object();
+        final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
+        final UiAutomation automan = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
+                checkServiceEnabled(waitObject, manager, serviceEnabled,
+                        VibratingAccessibilityService.class.getSimpleName());
+        try {
+            mAccessibilityManager.addAccessibilityServicesStateChangeListener(listener);
+
+            mVibratingAccessibilityServiceRule.enableService();
+
+            waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
+                    "Accessibility services state listener called when service is enabled");
+        } finally {
+            automan.destroy();
+        }
+    }
+
+    @Test
+    public void testAccessibilityServicesStateListenerWithExecutor() {
+        final Object waitObject = new Object();
+        final AtomicBoolean serviceEnabled = new AtomicBoolean(false);
+        final UiAutomation automan = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final AccessibilityServicesStateChangeListener listener = (AccessibilityManager manager) ->
+                checkServiceEnabled(waitObject, manager, serviceEnabled,
+                        VibratingAccessibilityService.class.getSimpleName());
+        try {
+            mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+                    mTargetContext.getMainExecutor(), listener);
+
+            mVibratingAccessibilityServiceRule.enableService();
+
+            waitForAtomicBooleanBecomes(serviceEnabled, true, waitObject,
+                    "Accessibility services state listener called when service is enabled");
+        } finally {
+            automan.destroy();
+        }
+    }
+
+
+    @Test
     public void testAccessibilityStateListenerNoHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -369,19 +450,83 @@
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener);
         mSpeakingAndVibratingAccessibilityServiceRule.enableService();
-        assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
-                "Accessibility state listener not called when services enabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+                "Accessibility state listener called when services enabled");
         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
                 mAccessibilityManager.isEnabled());
         InstrumentedAccessibilityService.disableAllServices();
-        assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
-                "Accessibility state listener not called when services disabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+                "Accessibility state listener called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
                 mAccessibilityManager.isEnabled());
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
     }
 
     @Test
+    public void testAudioDescriptionRequestedChangeListenerWithExecutor() {
+        final UiAutomation automan = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final Object waitObject = new Object();
+        final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
+
+        AudioDescriptionRequestedChangeListener listener = (boolean b) -> {
+            synchronized (waitObject) {
+                atomicBoolean.set(b);
+                waitObject.notifyAll();
+            }
+        };
+
+        try {
+            mAccessibilityManager.addAudioDescriptionRequestedChangeListener(
+                    mTargetContext.getMainExecutor(), listener);
+            putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
+            waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+                    "Audio description state listener called when services enabled");
+            assertTrue("Listener told that audio description by default is request.",
+                    mAccessibilityManager.isAudioDescriptionRequested());
+
+            putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
+            waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+                    "Audio description state listener called when services disabled");
+            assertFalse("Listener told that audio description by default is not request.",
+                    mAccessibilityManager.isAudioDescriptionRequested());
+            assertTrue(
+                    mAccessibilityManager.removeAudioDescriptionRequestedChangeListener(
+                            listener));
+        } finally {
+            automan.destroy();
+        }
+    }
+
+    @Test
+    public void testIsAudioDescriptionEnabled() throws Exception {
+        final UiAutomation automan = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+
+        try {
+            putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "1");
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return mAccessibilityManager.isAudioDescriptionRequested();
+                }
+            });
+            assertTrue(mAccessibilityManager.isAudioDescriptionRequested());
+
+            putSecureSetting(automan, ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, "0");
+            PollingCheck.waitFor(new PollingCheck.PollingCheckCondition() {
+                @Override
+                public boolean canProceed() {
+                    return !mAccessibilityManager.isAudioDescriptionRequested();
+                }
+            });
+            assertFalse(mAccessibilityManager.isAudioDescriptionRequested());
+        } finally {
+            automan.destroy();
+        }
+    }
+
+    @Test
     public void testAccessibilityStateListenerWithHandler() throws Exception {
         final Object waitObject = new Object();
         final AtomicBoolean atomicBoolean = new AtomicBoolean(false);
@@ -394,13 +539,13 @@
         };
         mAccessibilityManager.addAccessibilityStateChangeListener(listener, mHandler);
         mSpeakingAndVibratingAccessibilityServiceRule.enableService();
-        assertAtomicBooleanBecomes(atomicBoolean, true, waitObject,
-                "Accessibility state listener not called when services enabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, true, waitObject,
+                "Accessibility state listener called when services enabled");
         assertTrue("Listener told that accessibility is enabled, but manager says disabled",
                 mAccessibilityManager.isEnabled());
         InstrumentedAccessibilityService.disableAllServices();
-        assertAtomicBooleanBecomes(atomicBoolean, false, waitObject,
-                "Accessibility state listener not called when services disabled");
+        waitForAtomicBooleanBecomes(atomicBoolean, false, waitObject,
+                "Accessibility state listener called when services disabled");
         assertFalse("Listener told that accessibility is disabled, but manager says enabled",
                 mAccessibilityManager.isEnabled());
         mAccessibilityManager.removeAccessibilityStateChangeListener(listener);
@@ -437,18 +582,26 @@
         }
     }
 
-    private void assertAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
-            boolean expectedValue, Object waitObject, String message)
-            throws Exception {
-        long timeoutTime =
-                System.currentTimeMillis() + TIMEOUT_SERVICE_ENABLE;
+    private void checkServiceEnabled(Object waitObject, AccessibilityManager manager,
+            AtomicBoolean serviceEnabled, String serviceName) {
         synchronized (waitObject) {
-            while ((atomicBoolean.get() != expectedValue)
-                    && (System.currentTimeMillis() < timeoutTime)) {
-                waitObject.wait(timeoutTime - System.currentTimeMillis());
+            List<AccessibilityServiceInfo> infos = manager.getEnabledAccessibilityServiceList(
+                    AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+            for (AccessibilityServiceInfo info : infos) {
+                final String serviceId = info.getId();
+                if (serviceId.endsWith(serviceName)) {
+                    serviceEnabled.set(true);
+                    waitObject.notifyAll();
+                }
             }
         }
-        assertTrue(message, atomicBoolean.get() == expectedValue);
+    }
+
+    private void waitForAtomicBooleanBecomes(AtomicBoolean atomicBoolean,
+            boolean expectedValue, Object waitObject, String condition) {
+        long timeoutTime = System.currentTimeMillis() + TIMEOUT_SERVICE_ENABLE;
+        TestUtils.waitOn(waitObject, () -> atomicBoolean.get() == expectedValue, timeoutTime,
+                condition);
     }
 
     private void waitForAccessibilityEnabled() throws InterruptedException {
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 3b56fb5..7a004ee 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -18,7 +18,8 @@
 
 import static androidx.test.InstrumentationRegistry.getContext;
 
-import static org.junit.Assert.assertArrayEquals;
+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.assertNull;
@@ -42,7 +43,6 @@
 import android.text.style.ReplacementSpan;
 import android.util.ArrayMap;
 import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.accessibility.AccessibilityNodeInfo.CollectionInfo;
@@ -113,33 +113,13 @@
     }
 
     /**
-     * Tests if {@link AccessibilityNodeInfo}s are properly reused.
-     */
-    @SmallTest
-    @Test
-    public void testReuse() {
-        AccessibilityEvent firstInfo = AccessibilityEvent.obtain();
-        firstInfo.recycle();
-        AccessibilityEvent secondInfo = AccessibilityEvent.obtain();
-        assertSame("AccessibilityNodeInfo not properly reused", firstInfo, secondInfo);
-    }
-
-    /**
-     * Tests if {@link AccessibilityNodeInfo} are properly recycled.
+     * Tests if {@link AccessibilityNodeInfo} can be acquired through obtain(),
+     * and that recycle() can be called on the returned object.
      */
     @SmallTest
     @Test
     public void testRecycle() {
-        // obtain and populate an node info
-        AccessibilityNodeInfo populatedInfo = AccessibilityNodeInfo.obtain();
-        fullyPopulateAccessibilityNodeInfo(populatedInfo);
-
-        // recycle and obtain the same recycled instance
-        populatedInfo.recycle();
-        AccessibilityNodeInfo recycledInfo = AccessibilityNodeInfo.obtain();
-
-        // check expectations
-        assertAccessibilityNodeInfoCleared(recycledInfo);
+        AccessibilityNodeInfo.obtain().recycle();
     }
 
     /**
@@ -328,6 +308,7 @@
         // Populate 10 fields
         info.setMovementGranularities(AccessibilityNodeInfo.MOVEMENT_GRANULARITY_LINE);
         info.setViewIdResourceName("foo.bar:id/baz");
+        info.setUniqueId("foo.bar:id/baz10");
         info.setDrawingOrder(5);
         info.setAvailableExtraData(
                 Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
@@ -345,7 +326,8 @@
         info.setRangeInfo(RangeInfo.obtain(RangeInfo.RANGE_TYPE_FLOAT, 0.05f, 1.0f, 0.01f));
         info.setCollectionInfo(
                 CollectionInfo.obtain(2, 2, true, CollectionInfo.SELECTION_MODE_MULTIPLE));
-        info.setCollectionItemInfo(CollectionItemInfo.obtain(1, 2, 3, 4, true, true));
+        info.setCollectionItemInfo(CollectionItemInfo.obtain("RowTitle", 1, 2, "ColumnTitle",
+                3, 4, true, true));
         info.setParent(new View(getContext()));
         info.setSource(new View(getContext())); // Populates 2 fields: source and window id
         info.setLeashedParent(new MockBinder(), 1); // Populates 2 fields
@@ -471,6 +453,8 @@
                 receivedInfo.getMovementGranularities());
         assertEquals("viewId has incorrect value", expectedInfo.getViewIdResourceName(),
                 receivedInfo.getViewIdResourceName());
+        assertEquals("Unique id has incorrect value", expectedInfo.getUniqueId(),
+            receivedInfo.getUniqueId());
         assertEquals("drawing order has incorrect value", expectedInfo.getDrawingOrder(),
                 receivedInfo.getDrawingOrder());
         assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
@@ -544,6 +528,9 @@
             assertEquals("CollectionItemInfo#getRowSpan has incorrect value",
                     expectedItemInfo.getRowSpan(),
                     receivedItemInfo.getRowSpan());
+            assertThat(expectedItemInfo.getRowTitle()).isEqualTo(receivedItemInfo.getRowTitle());
+            assertThat(
+                    expectedItemInfo.getColumnTitle()).isEqualTo(receivedItemInfo.getColumnTitle());
         }
 
         // Check 1 field
@@ -650,6 +637,7 @@
         assertSame("movementGranularities not properly recycled", 0,
                 info.getMovementGranularities());
         assertNull("viewId not properly recycled", info.getViewIdResourceName());
+        assertNull("Unique id not properly recycled", info.getUniqueId());
         assertEquals(0, info.getDrawingOrder());
         assertTrue(info.getAvailableExtraData().isEmpty());
         assertNull("Pane title not properly recycled", info.getPaneTitle());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
index 9ffdb0d..e75a698 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfo_CollectionItemInfoTest.java
@@ -16,6 +16,8 @@
 
 package android.view.accessibility.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
@@ -50,11 +52,15 @@
 
         c = CollectionItemInfo.obtain(0, 1, 2, 3, true);
         assertNotNull(c);
-        verifyCollectionItemInfo(c, 0, 1, 2, 3, true, false);
+        verifyCollectionItemInfo(c, null, 0, 1, null, 2, 3, true, false);
 
         c = CollectionItemInfo.obtain(4, 5, 6, 7, true, true);
         assertNotNull(c);
-        verifyCollectionItemInfo(c, 4, 5, 6, 7, true, true);
+        verifyCollectionItemInfo(c, null, 4, 5, null, 6, 7, true, true);
+
+        c = CollectionItemInfo.obtain("RowTitle", 8, 9, "ColumnTitle", 10, 11, true, true);
+        assertNotNull(c);
+        verifyCollectionItemInfo(c, "RowTitle", 8, 9, "ColumnTitle", 10, 11, true, true);
     }
 
     @SmallTest
@@ -63,20 +69,34 @@
         CollectionItemInfo c;
 
         c = new CollectionItemInfo(0, 1, 2, 3, true);
-        verifyCollectionItemInfo(c, 0, 1, 2, 3, true, false);
+        verifyCollectionItemInfo(c, null, 0, 1, null, 2, 3, true, false);
 
         c = new CollectionItemInfo(4, 5, 6, 7, true, true);
-        verifyCollectionItemInfo(c, 4, 5, 6, 7, true, true);
+        verifyCollectionItemInfo(c, null, 4, 5, null, 6, 7, true, true);
+    }
+
+    @SmallTest
+    @Test
+    public void testBuilder() {
+        CollectionItemInfo.Builder builder = new CollectionItemInfo.Builder();
+
+        CollectionItemInfo collectionItemInfo = builder.setRowTitle("RowTitle").setRowIndex(
+                0).setRowSpan(1).setColumnTitle("ColumnTitle").setColumnIndex(2).setColumnSpan(
+                        3).setHeading(true).setSelected(true).build();
+        verifyCollectionItemInfo(collectionItemInfo, "RowTitle", 0, 1, "ColumnTitle", 2,
+                3, true, true);
     }
 
     /**
      * Verifies all properties of the <code>info</code> with input expected values.
      */
     public static void verifyCollectionItemInfo(AccessibilityNodeInfo.CollectionItemInfo info,
-            int rowIndex, int rowSpan, int columnIndex, int columnSpan, boolean heading,
-            boolean selected) {
+            String rowTitle, int rowIndex, int rowSpan, String columnTitle, int columnIndex,
+            int columnSpan, boolean heading, boolean selected) {
+        assertThat(rowTitle).isEqualTo(info.getRowTitle());
         assertEquals(rowIndex, info.getRowIndex());
         assertEquals(rowSpan, info.getRowSpan());
+        assertThat(columnTitle).isEqualTo(info.getColumnTitle());
         assertEquals(columnIndex, info.getColumnIndex());
         assertEquals(columnSpan, info.getColumnSpan());
         assertSame(heading, info.isHeading());
diff --git a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 08d6637..c3c5641 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -23,6 +23,7 @@
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.os.Message;
 import android.platform.test.annotations.Presubmit;
+import android.view.Display;
 import android.view.accessibility.AccessibilityRecord;
 
 import androidx.test.filters.SmallTest;
@@ -104,6 +105,8 @@
        TestCase.assertSame("scrollX not properly recycled", 0, record.getScrollX());
        TestCase.assertSame("scrollY not properly recycled", 0, record.getScrollY());
        TestCase.assertSame("toIndex not properly recycled", -1, record.getToIndex());
+       TestCase.assertSame("displayId not properly recycled", Display.INVALID_DISPLAY,
+               record.getDisplayId());
    }
 
     /**
@@ -132,6 +135,7 @@
         record.setScrollY(1);
         record.setToIndex(1);
         record.setScrollable(true);
+        record.setDisplayId(Display.DEFAULT_DISPLAY);
     }
 
     static void assertEqualAccessibilityRecord(AccessibilityRecord expectedRecord,
@@ -173,6 +177,8 @@
                 receivedRecord.getToIndex());
         assertSame("scrollable has incorrect value", expectedRecord.isScrollable(),
                 receivedRecord.isScrollable());
+        assertSame("displayId has incorrect value", expectedRecord.getDisplayId(),
+                receivedRecord.getDisplayId());
 
         assertFalse("one of the parcelableData is null",
                 expectedRecord.getParcelableData() == null
diff --git a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
index c3054ef..c6376af 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/CaptioningManagerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Matchers.anyFloat;
 import static org.mockito.Matchers.anyObject;
 import static org.mockito.Mockito.mock;
@@ -29,8 +30,10 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyZeroInteractions;
 
+import android.Manifest;
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.app.UiAutomation;
+import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
 import android.view.accessibility.CaptioningManager;
 import android.view.accessibility.CaptioningManager.CaptionStyle;
@@ -82,6 +85,10 @@
         putSecureSetting("accessibility_captioning_preset", "1");
         putSecureSetting("accessibility_captioning_locale", "en_US");
         putSecureSetting("accessibility_captioning_font_scale", "1.0");
+        // TODO (b209352162): We need to backup and restore the original values for
+        //  these two setting keys.
+        putSecureSetting("odi_captions_enabled", "0");
+        putSecureSetting("odi_captions_volume_ui_enabled", "0");
 
         CaptioningChangeListener mockListener = mock(CaptioningChangeListener.class);
         mManager.addCaptioningChangeListener(mockListener);
@@ -100,11 +107,19 @@
         putSecureSetting("accessibility_captioning_font_scale", "2.0");
         verify(mockListener, timeout(LISTENER_TIMEOUT)).onFontScaleChanged(anyFloat());
 
+        putSecureSetting("odi_captions_enabled", "1");
+        verify(mockListener, timeout(LISTENER_TIMEOUT)).onSystemAudioCaptioningChanged(true);
+
+        putSecureSetting("odi_captions_volume_ui_enabled", "1");
+        verify(mockListener, timeout(LISTENER_TIMEOUT)).onSystemAudioCaptioningUiChanged(true);
+
         mManager.removeCaptioningChangeListener(mockListener);
 
         Mockito.reset(mockListener);
 
         putSecureSetting("accessibility_captioning_enabled","0");
+        putSecureSetting("odi_captions_enabled", "0");
+        putSecureSetting("odi_captions_volume_ui_enabled", "0");
         verifyZeroInteractions(mockListener);
 
         try {
@@ -145,6 +160,52 @@
         assertNull("Default user style has no typeface", userStyle.getTypeface());
     }
 
+    @Test
+    public void testIsCallCaptioningEnabled() {
+        Resources resources =
+                getInstrumentation().getTargetContext().getResources();
+        // com.android.internal.R is not visible to this test so we need
+        // to query for the resource id.
+        int resourceId = resources.getIdentifier(
+                "config_systemCaptionsServiceCallsEnabled", "bool", "android");
+
+        boolean expected = resources.getBoolean(resourceId);
+
+        boolean actual = mManager.isCallCaptioningEnabled();
+
+        assertEquals(expected, actual);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSetSystemAudioCaptionWithoutPermission_throwSecurityException() {
+        mManager.setSystemAudioCaptioningRequested(true);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSetSystemAudioCaptionUiWithoutPermission_throwSecurityException() {
+        mManager.setSystemAudioCaptioningUiRequested(true);
+    }
+
+    @Test
+    public void testSystemAudioCaption() {
+        putSecureSetting("odi_captions_enabled", "0");
+        putSecureSetting("odi_captions_volume_ui_enabled", "0");
+        mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.SET_SYSTEM_AUDIO_CAPTION);
+        try {
+            mManager.setSystemAudioCaptioningRequested(true);
+            assertTrue("Test runner set system audio caption enabled to true",
+                    mManager.isSystemAudioCaptioningRequested());
+
+            mManager.setSystemAudioCaptioningUiRequested(true);
+            assertTrue("Test runner set system audio caption ui enabled to true",
+                    mManager.isSystemAudioCaptioningUiRequested());
+        } finally {
+            mUiAutomation.dropShellPermissionIdentity();
+            putSecureSetting("odi_captions_enabled", "0");
+            putSecureSetting("odi_captions_volume_ui_enabled", "0");
+        }
+    }
+
     private void deleteSecureSetting(String name) {
         execShellCommand("settings delete secure " + name);
     }
diff --git a/tests/accessibilityservice/AndroidManifest.xml b/tests/accessibilityservice/AndroidManifest.xml
index 84981cd..584a8d5 100644
--- a/tests/accessibilityservice/AndroidManifest.xml
+++ b/tests/accessibilityservice/AndroidManifest.xml
@@ -25,6 +25,7 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS"/>
     <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
 
     <application android:theme="@android:style/Theme.Holo.NoActionBar"
          android:requestLegacyExternalStorage="true">
@@ -57,6 +58,11 @@
              android:supportsPictureInPicture="true"
              android:screenOrientation="locked"/>
 
+        <activity android:label="Activity for testing window accessibility reporting"
+                  android:name=".activities.NotTouchableWindowTestActivity"
+                  android:process=":NotTouchableWindowTestActivity"
+                  android:exported="true"/>
+
         <activity android:label="@string/non_default_display_activity"
                   android:name=".activities.NonDefaultDisplayActivity"
                   android:screenOrientation="locked"/>
@@ -77,6 +83,9 @@
         <activity android:label="@string/accessibility_drag_and_drop_test_activity"
                   android:name=".activities.AccessibilityDragAndDropActivity"
                   android:screenOrientation="locked"/>
+        <activity android:label="@string/accessibility_cache_activity"
+                  android:name=".activities.AccessibilityCacheActivity"
+                  android:screenOrientation="locked"/>
 
         <service android:name=".StubSystemActionsAccessibilityService"
              android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
@@ -194,6 +203,17 @@
             <meta-data android:name="android.accessibilityservice"
                        android:resource="@xml/stub_focus_indicator_service"/>
         </service>
+
+        <service android:name=".StubInputMethod"
+            android:label="@string/ime_name"
+            android:permission="android.permission.BIND_INPUT_METHOD"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.view.InputMethod"/>
+            </intent-filter>
+            <meta-data android:name="android.view.im"
+                android:resource="@xml/stub_ime"/>
+        </service>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/accessibilityservice/res/layout/accessibility_cache.xml b/tests/accessibilityservice/res/layout/accessibility_cache.xml
new file mode 100644
index 0000000..07b7f9c
--- /dev/null
+++ b/tests/accessibilityservice/res/layout/accessibility_cache.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+              android:orientation="vertical">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:orientation="horizontal">
+        <TextView
+            android:text="textView"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:layout_margin="60dp"/>
+    </LinearLayout>
+</LinearLayout>
diff --git a/tests/accessibilityservice/res/values/strings.xml b/tests/accessibilityservice/res/values/strings.xml
index 3d3b6be..bbc55619 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -199,4 +199,8 @@
     <string name="accessibility_drag_and_drop_test_activity">Drag and drop</string>
     <string name="drag_and_drop_text">Dragged text</string>
 
+    <!-- String label of the input method -->
+    <string name="ime_name">InputMethod</string>
+
+    <string name="accessibility_cache_activity">Cache activity</string>
 </resources>
diff --git a/tests/accessibilityservice/res/xml/stub_ime.xml b/tests/accessibilityservice/res/xml/stub_ime.xml
new file mode 100644
index 0000000..9810de4
--- /dev/null
+++ b/tests/accessibilityservice/res/xml/stub_ime.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+
+<input-method xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportsSwitchingToNextInputMethod="true">
+    <subtype
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="true"
+        android:overridesImplicitlyEnabledSubtype="true"
+        />
+    <subtype
+        android:imeSubtypeLocale="en_US"
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="true"
+        />
+    <subtype
+        android:imeSubtypeLocale="ru_RU"
+        android:imeSubtypeMode="keyboard"
+        android:isAsciiCapable="false"
+        />
+</input-method>
\ No newline at end of file
diff --git a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
index 5797079..c12f8c7 100644
--- a/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
+++ b/tests/accessibilityservice/res/xml/stub_touch_exploration_a11y_service.xml
@@ -20,7 +20,7 @@
         android:description="@string/stub_touch_exploration_a11y_service_description"
         android:accessibilityEventTypes="typeAllMask"
         android:accessibilityFeedbackType="feedbackGeneric"
-        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds|flagSendMotionEvents"
+        android:accessibilityFlags="flagDefault|flagRequestTouchExplorationMode|flagReportViewIds|flagSendMotionEvents|flagRetrieveInteractiveWindows"
         android:canRequestTouchExplorationMode="true"
         android:canRetrieveWindowContent="true"
         android:canPerformGestures="true" />
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java
new file mode 100644
index 0000000..08710d6
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityCacheTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibilityservice.cts.activities.AccessibilityCacheActivity;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.platform.test.annotations.AppModeFull;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+@AppModeFull
+@RunWith(AndroidJUnit4.class)
+public class AccessibilityCacheTest {
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private InstrumentedAccessibilityService mService;
+    private AccessibilityCacheActivity mActivity;
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    private final ActivityTestRule<AccessibilityCacheActivity> mActivityRule =
+            new ActivityTestRule<>(AccessibilityCacheActivity.class, false, false);
+
+    private InstrumentedAccessibilityServiceTestRule<InstrumentedAccessibilityService>
+            mInstrumentedAccessibilityServiceRule = new InstrumentedAccessibilityServiceTestRule<>(
+            InstrumentedAccessibilityService.class, false);
+
+    @Rule
+    public final RuleChain mRuleChain = RuleChain
+            .outerRule(mActivityRule)
+            .around(mInstrumentedAccessibilityServiceRule)
+            .around(mDumpOnFailureRule);
+
+    @BeforeClass
+    public static void oneTimeSetup() throws Exception {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        sUiAutomation = sInstrumentation.getUiAutomation();
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mService = mInstrumentedAccessibilityServiceRule.enableService();
+        mActivity = launchActivityAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, mActivityRule);
+    }
+
+    @Test
+    public void enable_cacheEnabled() {
+        assertTrue(mService.setCacheEnabled(false));
+        assertFalse("Failed to disable", mService.isCacheEnabled());
+
+        assertTrue(mService.setCacheEnabled(true));
+        assertTrue("Failed to enable", mService.isCacheEnabled());
+    }
+
+    @Test
+    public void disable_cacheDisabled() {
+        assertTrue(mService.setCacheEnabled(false));
+        assertFalse("Failed to disable", mService.isCacheEnabled());
+    }
+
+    @Test
+    public void queryNode_nodeIsInCache() {
+        AccessibilityNodeInfo info = mService.getRootInActiveWindow();
+        assertTrue("Node is not in cache", mService.isNodeInCache(info));
+    }
+
+    @Test
+    public void invalidateNode_nodeInCacheInvalidated() {
+        AccessibilityNodeInfo info = mService.getRootInActiveWindow();
+        assertTrue(mService.clearCachedSubtree(info));
+        assertFalse("Node is still in cache", mService.isNodeInCache(info));
+    }
+
+    @Test
+    public void invalidateNode_subtreeInCacheInvalidated() {
+        // Tree is FrameLayout with 1 TextView child
+        AccessibilityNodeInfo root = mService.getRootInActiveWindow();
+        assertThat(root.getChildCount(), is(1));
+        AccessibilityNodeInfo child = root.getChild(0);
+
+        assertTrue(mService.clearCachedSubtree(root));
+
+        assertFalse("Root is in cache", mService.isNodeInCache(root));
+        assertFalse("Child 1 is in cache", mService.isNodeInCache(child));
+    }
+
+    @Test
+    public void clear_cacheInvalidated() {
+        // Tree is FrameLayout with 1 TextView child
+        AccessibilityNodeInfo root = mService.getRootInActiveWindow();
+        assertThat(root.getChildCount(), is(1));
+        AccessibilityNodeInfo child = root.getChild(0);
+
+        assertTrue(mService.clearCache());
+
+        assertFalse("Root is in cache", mService.isNodeInCache(root));
+        assertFalse("Child 1 is in cache", mService.isNodeInCache(child));
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 1b003e3..5f9733c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,6 +16,7 @@
 
 package android.accessibilityservice.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
 import static android.accessibility.cts.common.InstrumentedAccessibilityService.enableService;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
 import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventTypeWithAction;
@@ -99,6 +100,7 @@
 
 import com.android.compatibility.common.util.CtsMouseUtil;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -159,10 +161,16 @@
 
     @Before
     public void setUp() throws Exception {
+        sUiAutomation.adoptShellPermissionIdentity(POST_NOTIFICATIONS);
         mActivity = launchActivityAndWaitForItToBeOnscreen(
                 sInstrumentation, sUiAutomation, mActivityRule);
     }
 
+    @After
+    public void tearDown() throws Exception {
+        sUiAutomation.dropShellPermissionIdentity();
+    }
+
     @MediumTest
     @Presubmit
     @Test
@@ -172,6 +180,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_SELECTED);
         expected.setClassName(ListView.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(mActivity.getString(R.string.second_list_item));
         expected.setItemCount(2);
         expected.setCurrentItemIndex(1);
@@ -215,6 +224,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_CLICKED);
         expected.setClassName(Button.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(mActivity.getString(R.string.button_title));
         expected.setEnabled(true);
 
@@ -253,6 +263,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
         expected.setClassName(Button.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(mActivity.getString(R.string.button_title));
         expected.setEnabled(true);
 
@@ -291,6 +302,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_FOCUSED);
         expected.setClassName(Button.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(mActivity.getString(R.string.button_title));
         expected.setItemCount(5);
         expected.setCurrentItemIndex(3);
@@ -345,6 +357,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
         expected.setClassName(EditText.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(afterText);
         expected.setBeforeText(beforeText);
         expected.setFromIndex(3);
@@ -385,6 +398,7 @@
         expected.setEventType(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
         expected.setClassName(AlertDialog.class.getName());
         expected.setPackageName(mActivity.getPackageName());
+        expected.setDisplayId(mActivity.getDisplayId());
         expected.getText().add(mActivity.getString(R.string.alert_title));
         expected.getText().add(mActivity.getString(R.string.alert_message));
         expected.setEnabled(true);
@@ -415,6 +429,25 @@
     }
 
     @MediumTest
+    @Presubmit
+    @Test
+    public void testTypeWindowsChangedAccessibilityEvent() throws Throwable {
+        // create and populate the expected event
+        final AccessibilityEvent expected = AccessibilityEvent.obtain();
+        expected.setEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED);
+        expected.setDisplayId(mActivity.getDisplayId());
+
+        // check the received event
+        AccessibilityEvent awaitedEvent =
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> mActivity.runOnUiThread(() -> mActivity.finish()),
+                    event -> event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_REMOVED
+                            && equalsAccessiblityEvent(event, expected),
+                    DEFAULT_TIMEOUT_MS);
+        assertNotNull("Did not receive expected event: " + expected, awaitedEvent);
+    }
+
+    @MediumTest
     @AppModeFull
     @SuppressWarnings("deprecation")
     @Presubmit
@@ -1108,6 +1141,7 @@
             && first.getScrollX() == second.getScrollX()
             && first.getScrollY() == second.getScrollY()
             && first.getAddedCount() == second.getAddedCount()
+            && first.getDisplayId() == second.getDisplayId()
             && TextUtils.equals(first.getBeforeText(), second.getBeforeText())
             && TextUtils.equals(first.getClassName(), second.getClassName())
             && TextUtils.equals(first.getContentDescription(), second.getContentDescription())
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
index 25f4853..b5b5766 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -16,8 +16,6 @@
 
 package android.accessibilityservice.cts;
 
-import static android.accessibility.cts.common.ShellCommandBuilder.execShellCommand;
-import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.homeScreenOrBust;
 
 import static org.junit.Assert.assertTrue;
@@ -174,7 +172,6 @@
         // Ideally should verify that we actually have a screenshot, but it's also possible
         // for the screenshot to fail
         waitForIdle();
-        execShellCommand(sUiAutomation, AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND);
     }
 
     private void waitForIdle() throws TimeoutException {
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
index 659aaf7..c1396d9 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityMagnificationTest.java
@@ -16,12 +16,10 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_WINDOW;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 
-import static androidx.test.InstrumentationRegistry.getInstrumentation;
-
-import static com.android.compatibility.common.util.TestUtils.waitOn;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -30,6 +28,7 @@
 import static org.mockito.Mockito.anyFloat;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
@@ -40,6 +39,7 @@
 import android.accessibilityservice.AccessibilityService.MagnificationController;
 import android.accessibilityservice.AccessibilityService.MagnificationController.OnMagnificationChangedListener;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.MagnificationConfig;
 import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
 import android.app.Activity;
 import android.app.Instrumentation;
@@ -51,22 +51,31 @@
 import android.util.DisplayMetrics;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
 import android.widget.Button;
 
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
- * Class for testing {@link AccessibilityServiceInfo}.
+ * Class for testing {@link MagnificationController} and the magnification overlay window.
  */
 @AppModeFull
 @RunWith(AndroidJUnit4.class)
@@ -74,8 +83,13 @@
 
     /** Maximum timeout when waiting for a magnification callback. */
     public static final int LISTENER_TIMEOUT_MILLIS = 500;
+    /** Maximum animation timeout when waiting for a magnification callback. */
+    public static final int LISTENER_ANIMATION_TIMEOUT_MILLIS = 1000;
     public static final String ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED =
             "accessibility_display_magnification_enabled";
+
+    private static UiAutomation sUiAutomation;
+
     private StubMagnificationAccessibilityService mService;
     private Instrumentation mInstrumentation;
 
@@ -100,12 +114,26 @@
             .around(mInstrumentedAccessibilityServiceRule)
             .around(mDumpOnFailureRule);
 
+    @BeforeClass
+    public static void oneTimeSetUp() {
+        sUiAutomation = InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+        final AccessibilityServiceInfo info = sUiAutomation.getServiceInfo();
+        info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        sUiAutomation.setServiceInfo(info);
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        sUiAutomation.destroy();
+    }
+
     @Before
     public void setUp() throws Exception {
-        ShellCommandBuilder.create(getInstrumentation())
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        ShellCommandBuilder.create(sUiAutomation)
                 .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                 .run();
-        mInstrumentation = getInstrumentation();
         // Starting the service will force the accessibility subsystem to examine its settings, so
         // it will update magnification in the process to disable it.
         mService = mMagnificationAccessibilityServiceRule.enableService();
@@ -159,8 +187,151 @@
     }
 
     @Test
+    public void testSetMagnificationConfig_expectedConfig() throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final float scale = 2.0f;
+        final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+        final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+        final AtomicBoolean setConfig = new AtomicBoolean();
+
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+
+        mService.runOnServiceSync(() -> {
+            setConfig.set(controller.setMagnificationConfig(config, false));
+        });
+        waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+        assertTrue("Failed to set config", setConfig.get());
+        assertConfigEquals(config, controller.getMagnificationConfig());
+
+        final float newScale = scale + 1;
+        final Region region = controller.getMagnificationRegion();
+        final Rect bounds = region.getBounds();
+        final float newX = bounds.left + (bounds.width() / 4.0f);
+        final float newY = bounds.top + (bounds.height() / 4.0f);
+        final MagnificationConfig newConfig = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN).setScale(newScale).setCenterX(
+                        newX).setCenterY(
+                        newY).build();
+        mService.runOnServiceSync(() -> {
+            controller.setMagnificationConfig(newConfig, false);
+        });
+        waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_FULLSCREEN, newX, newY);
+
+        assertTrue("Failed to set config", setConfig.get());
+        assertConfigEquals(newConfig, controller.getMagnificationConfig());
+    }
+
+    @Test
+    public void testSetMagnificationConfig_legacyApiExpectedResult() {
+        final MagnificationController controller = mService.getMagnificationController();
+        final Region region = controller.getMagnificationRegion();
+        final Rect bounds = region.getBounds();
+        final float scale = 2.0f;
+        final float x = bounds.left + (bounds.width() / 4.0f);
+        final float y = bounds.top + (bounds.height() / 4.0f);
+        final AtomicBoolean setConfig = new AtomicBoolean();
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+        try {
+            mService.runOnServiceSync(() -> {
+                setConfig.set(controller.setMagnificationConfig(config, false));
+            });
+
+            assertEquals("Failed to apply scale", scale, controller.getScale(), 0f);
+            assertEquals("Failed to apply center X", x, controller.getCenterX(), 5.0f);
+            assertEquals("Failed to apply center Y", y, controller.getCenterY(), 5.0f);
+        } finally {
+            final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+                    .setScale(1).build();
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(resetConfig, false);
+            });
+        }
+    }
+
+    @Test
+    public void testSetWindowModeConfig_hasMagnificationOverlay() throws TimeoutException {
+        final MagnificationController controller = mService.getMagnificationController();
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(2.0f)
+                .build();
+
+        try {
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> controller.setMagnificationConfig(config, false),
+                    event -> sUiAutomation.getWindows().stream().anyMatch(
+                            accessibilityWindowInfo -> accessibilityWindowInfo.getType()
+                                    == AccessibilityWindowInfo.TYPE_MAGNIFICATION_OVERLAY), 5000);
+        } finally {
+            controller.resetCurrentMagnification(false);
+        }
+    }
+
+    @Test
+    public void testGetMagnificationConfig_setConfigByLegacyApi_expectedResult() {
+        final MagnificationController controller = mService.getMagnificationController();
+        final Region region = controller.getMagnificationRegion();
+        final Rect bounds = region.getBounds();
+        final float scale = 2.0f;
+        final float x = bounds.left + (bounds.width() / 4.0f);
+        final float y = bounds.top + (bounds.height() / 4.0f);
+        mService.runOnServiceSync(() -> {
+            controller.setScale(scale, false);
+            controller.setCenter(x, y, false);
+        });
+
+        final MagnificationConfig config = controller.getMagnificationConfig();
+
+        assertEquals("Failed to apply scale", scale, config.getScale(), 0f);
+        assertEquals("Failed to apply center X", x, config.getCenterX(), 5.0f);
+        assertEquals("Failed to apply center Y", y, config.getCenterY(), 5.0f);
+    }
+
+    @Test
     public void testListener() {
         final MagnificationController controller = mService.getMagnificationController();
+        final OnMagnificationChangedListener spyListener = mock(
+                OnMagnificationChangedListener.class);
+        final OnMagnificationChangedListener listener =
+                (controller1, region, scale, centerX, centerY) ->
+                        spyListener.onMagnificationChanged(controller1, region, scale, centerX,
+                                centerY);
+        controller.addListener(listener);
+
+        try {
+            final float scale = 2.0f;
+            final AtomicBoolean result = new AtomicBoolean();
+
+            mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
+
+            assertTrue("Failed to set scale", result.get());
+            verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
+
+            mService.runOnServiceSync(() -> result.set(controller.reset(false)));
+
+            assertTrue("Failed to reset", result.get());
+            verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
+        } finally {
+            controller.removeListener(listener);
+        }
+    }
+
+    @Test
+    public void testListener_changeConfigByLegacyApi_notifyConfigChanged() {
+        final MagnificationController controller = mService.getMagnificationController();
         final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
         controller.addListener(listener);
 
@@ -171,21 +342,124 @@
             mService.runOnServiceSync(() -> result.set(controller.setScale(scale, false)));
 
             assertTrue("Failed to set scale", result.get());
-            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
-                    eq(controller), any(Region.class), eq(scale), anyFloat(), anyFloat());
+            final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                    MagnificationConfig.class);
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), configCaptor.capture());
+            assertEquals(scale, configCaptor.getValue().getScale(), 0);
 
+            reset(listener);
             mService.runOnServiceSync(() -> result.set(controller.reset(false)));
 
             assertTrue("Failed to reset", result.get());
-            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
-                    eq(controller), any(Region.class), eq(1.0f), anyFloat(), anyFloat());
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), configCaptor.capture());
+            assertEquals(1.0f, configCaptor.getValue().getScale(), 0);
         } finally {
             controller.removeListener(listener);
         }
     }
 
     @Test
-    public void testMagnificationServiceShutsDownWhileMagnifying_shouldReturnTo1x() {
+    public void testListener_magnificationConfigChangedWithoutAnimation_notifyConfigChanged()
+            throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+        controller.addListener(listener);
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final float scale = 2.0f;
+        final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+        final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+        final AtomicBoolean setConfig = new AtomicBoolean();
+
+        try {
+            mService.runOnServiceSync(() -> {
+                setConfig.set(controller.setMagnificationConfig(config, false));
+            });
+            TestUtils.waitUntil("Failed to set config", () -> setConfig.get());
+
+            final ArgumentCaptor<MagnificationConfig> configCaptor = ArgumentCaptor.forClass(
+                    MagnificationConfig.class);
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS).atLeastOnce()).onMagnificationChanged(
+                    eq(controller), any(Region.class), configCaptor.capture());
+            assertConfigEquals(config, configCaptor.getValue());
+
+            final float newScale = scale + 1;
+            final float newX = x + 10;
+            final float newY = y + 10;
+            final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
+                    .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                    .setScale(newScale)
+                    .setCenterX(newX)
+                    .setCenterY(newY).build();
+
+            reset(listener);
+            mService.runOnServiceSync(() -> {
+                setConfig.set(controller.setMagnificationConfig(fullscreenConfig, false));
+            });
+            TestUtils.waitUntil("Failed to set config", () -> setConfig.get());
+
+            verify(listener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), configCaptor.capture());
+            assertConfigEquals(fullscreenConfig, configCaptor.getValue());
+        } finally {
+            final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+                    .setScale(1).build();
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(resetConfig, false);
+                controller.removeListener(listener);
+            });
+        }
+    }
+
+    @Test
+    public void testListener_magnificationConfigChangedWithAnimation_notifyConfigChanged() {
+        final MagnificationController controller = mService.getMagnificationController();
+        final OnMagnificationChangedListener listener = mock(OnMagnificationChangedListener.class);
+        controller.addListener(listener);
+        final float scale = 2.0f;
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale).build();
+
+        try {
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(config, /* animate= */ true);
+            });
+
+            verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), any(MagnificationConfig.class));
+
+            final float newScale = scale + 1;
+            final MagnificationConfig fullscreenConfig = new MagnificationConfig.Builder()
+                    .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                    .setScale(newScale).build();
+
+            reset(listener);
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(fullscreenConfig, /* animate= */ true);
+            });
+
+            verify(listener, timeout(LISTENER_ANIMATION_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), any(MagnificationConfig.class));
+        } finally {
+            final MagnificationConfig resetConfig = new MagnificationConfig.Builder()
+                    .setScale(1).build();
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(resetConfig, false);
+                controller.removeListener(listener);
+            });
+        }
+    }
+
+    @Test
+    public void testMagnificationServiceShutsDownWhileMagnifying_fullscreen_shouldReturnTo1x() {
         final MagnificationController controller = mService.getMagnificationController();
         mService.runOnServiceSync(() -> controller.setScale(2.0f, false));
 
@@ -199,6 +473,36 @@
     }
 
     @Test
+    public void testMagnificationServiceShutsDownWhileMagnifying_windowMode_shouldReturnTo1x()
+            throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final float scale = 2.0f;
+        final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+        final float y = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+
+        mService.runOnServiceSync(() -> {
+            controller.setMagnificationConfig(config, false);
+        });
+        waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+        mService.runOnServiceSync(() -> mService.disableSelf());
+        mService = null;
+        InstrumentedAccessibilityService service =
+                mInstrumentedAccessibilityServiceRule.enableService();
+        final MagnificationController controller2 = service.getMagnificationController();
+        assertEquals("Magnification must reset when a service dies",
+                1.0f, controller2.getMagnificationConfig().getScale(), 0f);
+    }
+
+    @Test
     public void testGetMagnificationRegion_whenCanControlMagnification_shouldNotBeEmpty() {
         final MagnificationController controller = mService.getMagnificationController();
         Region magnificationRegion = controller.getMagnificationRegion();
@@ -220,7 +524,7 @@
 
     @Test
     public void testGetMagnificationRegion_whenMagnificationGesturesEnabled_shouldNotBeEmpty() {
-        ShellCommandBuilder.create(mInstrumentation)
+        ShellCommandBuilder.create(sUiAutomation)
                 .putSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, "1")
                 .run();
         mService.runOnServiceSync(() -> mService.disableSelf());
@@ -233,13 +537,103 @@
             assertFalse("Magnification region should not be empty when magnification "
                     + "gestures are active", magnificationRegion.isEmpty());
         } finally {
-            ShellCommandBuilder.create(mInstrumentation)
+            ShellCommandBuilder.create(sUiAutomation)
                     .deleteSecureSetting(ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED)
                     .run();
         }
     }
 
     @Test
+    public void testGetCurrentMagnificationRegion_fullscreen_exactRegionCenter() throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final Region region = controller.getMagnificationRegion();
+        final Rect bounds = region.getBounds();
+        final float scale = 2.0f;
+        final float x = bounds.left + (bounds.width() / 4.0f);
+        final float y = bounds.top + (bounds.height() / 4.0f);
+
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_FULLSCREEN)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+        try {
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(config, false);
+            });
+            waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_FULLSCREEN, x, y);
+
+            final Region magnificationRegion = controller.getCurrentMagnificationRegion();
+            assertFalse(magnificationRegion.isEmpty());
+        } finally {
+            mService.runOnServiceSync(() -> {
+                controller.resetCurrentMagnification(false);
+            });
+        }
+    }
+
+    @Test
+    public void testGetCurrentMagnificationRegion_windowMode_exactRegionCenter() throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final float scale = 2.0f;
+        final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+        final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+        try {
+            mService.runOnServiceSync(() -> {
+                controller.setMagnificationConfig(config, false);
+            });
+            waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+            final Region magnificationRegion = controller.getCurrentMagnificationRegion();
+            final Rect magnificationBounds = magnificationRegion.getBounds();
+            assertEquals(magnificationBounds.exactCenterX(), x, 0);
+            assertEquals(magnificationBounds.exactCenterY(), y, 0);
+        } finally {
+            mService.runOnServiceSync(() -> {
+                controller.resetCurrentMagnification(false);
+            });
+        }
+    }
+
+    @Test
+    public void testResetCurrentMagnificationRegion_resetWindowMode() throws Exception {
+        final MagnificationController controller = mService.getMagnificationController();
+        final WindowManager windowManager = mInstrumentation.getContext().getSystemService(
+                WindowManager.class);
+        final float scale = 2.0f;
+        final float x = windowManager.getCurrentWindowMetrics().getBounds().centerX();
+        final float y = windowManager.getCurrentWindowMetrics().getBounds().centerY();
+
+        final MagnificationConfig config = new MagnificationConfig.Builder()
+                .setMode(MAGNIFICATION_MODE_WINDOW)
+                .setScale(scale)
+                .setCenterX(x)
+                .setCenterY(y).build();
+
+        mService.runOnServiceSync(() -> {
+            controller.setMagnificationConfig(config, false);
+        });
+        waitUntilMagnificationConfig(controller, MAGNIFICATION_MODE_WINDOW, x, y);
+
+        assertEquals(scale, controller.getMagnificationConfig().getScale(), 0);
+
+        mService.runOnServiceSync(() -> {
+            controller.resetCurrentMagnification(false);
+        });
+
+        assertEquals(1.0f, controller.getMagnificationConfig().getScale(), 0);
+        assertTrue(controller.getCurrentMagnificationRegion().isEmpty());
+    }
+
+    @Test
     public void testAnimatingMagnification() throws InterruptedException {
         final MagnificationController controller = mService.getMagnificationController();
         final int timeBetweenAnimationChanges = 100;
@@ -283,17 +677,15 @@
     @Test
     public void testA11yNodeInfoVisibility_whenOutOfMagnifiedArea_shouldVisible()
             throws Exception{
-        final UiAutomation uiAutomation = mInstrumentation.getUiAutomation(
-                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
         final Activity activity = launchActivityAndWaitForItToBeOnscreen(
-                mInstrumentation, uiAutomation, mActivityRule);
+                mInstrumentation, sUiAutomation, mActivityRule);
         final MagnificationController controller = mService.getMagnificationController();
         final Rect magnifyBounds = controller.getMagnificationRegion().getBounds();
         final float scale = 8.0f;
         final Button button = activity.findViewById(R.id.button1);
         adjustViewBoundsIfNeeded(button, scale, magnifyBounds);
 
-        final AccessibilityNodeInfo buttonNode = uiAutomation.getRootInActiveWindow()
+        final AccessibilityNodeInfo buttonNode = sUiAutomation.getRootInActiveWindow()
                 .findAccessibilityNodeInfosByViewId(
                         "android.accessibilityservice.cts:id/button1").get(0);
         assertNotNull("Can't find button on the screen", buttonNode);
@@ -330,31 +722,32 @@
 
     private void waitOnMagnificationChanged(MagnificationController controller, float newScale,
             float newCenterX, float newCenterY) {
-        final Object waitLock = new Object();
-        final AtomicBoolean notified = new AtomicBoolean();
-        final OnMagnificationChangedListener listener = (c, region, scale, centerX, centerY) -> {
-            final float delta = 5.0f;
-            synchronized (waitLock) {
-                if (newScale == scale
-                        && (centerX > newCenterX - delta) && (centerY > newCenterY - delta)) {
-                    notified.set(true);
-                    waitLock.notifyAll();
-                }
-            }
-        };
+        final OnMagnificationChangedListener spyListener = mock(
+                OnMagnificationChangedListener.class);
+        final OnMagnificationChangedListener listener =
+                (controller1, region, scale, centerX, centerY) ->
+                        spyListener.onMagnificationChanged(controller1, region, scale, centerX,
+                                centerY);
         controller.addListener(listener);
         try {
             final AtomicBoolean setScale = new AtomicBoolean();
             final AtomicBoolean setCenter = new AtomicBoolean();
             mService.runOnServiceSync(() -> {
                 setScale.set(controller.setScale(newScale, false));
+            });
+
+            assertTrue("Failed to set scale", setScale.get());
+            verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), eq(newScale), anyFloat(), anyFloat());
+
+            reset(spyListener);
+            mService.runOnServiceSync(() -> {
                 setCenter.set(controller.setCenter(newCenterX, newCenterY, false));
             });
-            assertTrue("Failed to set scale", setScale.get());
-            assertEquals("Failed to apply scale", newScale, controller.getScale(), 0f);
+
             assertTrue("Failed to set center", setCenter.get());
-            waitOn(waitLock, () -> notified.get(), LISTENER_TIMEOUT_MILLIS,
-                    "waitOnMagnificationChanged");
+            verify(spyListener, timeout(LISTENER_TIMEOUT_MILLIS)).onMagnificationChanged(
+                    eq(controller), any(Region.class), anyFloat(), eq(newCenterX), eq(newCenterY));
         } finally {
             controller.removeListener(listener);
         }
@@ -396,4 +789,25 @@
         // Waiting for UI refresh
         mInstrumentation.waitForIdleSync();
     }
+
+    private void waitUntilMagnificationConfig(MagnificationController controller, int mode,
+            float centerX, float centerY) throws Exception {
+        TestUtils.waitUntil("Failed to apply the config.",
+                () -> {
+                    final MagnificationConfig tmpConfig = controller.getMagnificationConfig();
+                    return tmpConfig.getMode() == mode && tmpConfig.getCenterX() == centerX
+                            && tmpConfig.getCenterY() == centerY;
+                });
+    }
+
+    private void assertConfigEquals(MagnificationConfig expected, MagnificationConfig result) {
+        assertEquals("Failed to apply mode", expected.getMode(),
+                result.getMode(), 0f);
+        assertEquals("Failed to apply scale", expected.getScale(),
+                result.getScale(), 0f);
+        assertEquals("Failed to apply center X", expected.getCenterX(),
+                result.getCenterX(), 5.0f);
+        assertEquals("Failed to apply center Y", expected.getCenterY(),
+                result.getCenterY(), 5.0f);
+    }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
index 5744ac9..5f100b4 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardTest.java
@@ -18,10 +18,13 @@
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_AUTO;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN;
 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_IGNORE_HARD_KEYBOARD;
+import static android.accessibilityservice.AccessibilityService.SoftKeyboardController.ENABLE_IME_SUCCESS;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
@@ -136,19 +139,74 @@
         String currentIME = Settings.Secure.getString(
                 mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
         assertNotEquals(Ime1Constants.IME_ID, currentIME);
-        // Enable a dummy IME for this test.
-        try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID)) {
-            // Switch to the dummy IME.
+        // Enable a placeholder IME for this test.
+        try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID, true)) {
+            // Switch to the placeholder IME.
             final boolean success = controller.switchToInputMethod(Ime1Constants.IME_ID);
             currentIME = Settings.Secure.getString(
                     mService.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
 
-            // The current IME should be set to the dummy IME successfully.
+            // The current IME should be set to the placeholder IME successfully.
             assertTrue(success);
             assertEquals(Ime1Constants.IME_ID, currentIME);
         }
     }
 
+    @Test
+    public void testSetInputMethodEnabled_differentPackage() throws Exception {
+        // Disable a placeholder IME for this test.
+        try (TestImeSession imeSession = new TestImeSession(Ime1Constants.IME_ID, false)) {
+            final SoftKeyboardController controller = mService.getSoftKeyboardController();
+
+            String enabledIMEs = Settings.Secure.getString(
+                    mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+            assertFalse(enabledIMEs.contains(Ime1Constants.IME_ID));
+
+            // Enable the placeholder IME.
+            try {
+                int result = controller.setInputMethodEnabled(Ime1Constants.IME_ID, true);
+                fail("should have thrown SecurityException");
+            } catch (SecurityException ignored) {
+            }
+
+            enabledIMEs = Settings.Secure.getString(
+                    mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+            // The placeholder IME should not be enabled;
+            assertFalse(enabledIMEs.contains(Ime1Constants.IME_ID));
+        }
+    }
+
+    @Test
+    public void testSetInputMethodEnabled_success() throws Exception {
+        String ImeId = "android.accessibilityservice.cts/.StubInputMethod";
+        // Disable a placeholder IME for this test.
+        try (TestImeSession imeSession = new TestImeSession(ImeId, false)) {
+            final SoftKeyboardController controller = mService.getSoftKeyboardController();
+
+            String enabledIMEs = Settings.Secure.getString(
+                    mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+            assertFalse(enabledIMEs.contains(ImeId));
+
+            // Enable the placeholder IME.
+            int result = controller.setInputMethodEnabled(ImeId, true);
+            enabledIMEs = Settings.Secure.getString(
+                    mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+
+            // The placeholder IME should be enabled;
+            assertEquals(ENABLE_IME_SUCCESS, result);
+            assertTrue(enabledIMEs.contains(ImeId));
+
+            // Disable the placeholder IME.
+            result = controller.setInputMethodEnabled(ImeId, false);
+            enabledIMEs = Settings.Secure.getString(
+                    mService.getContentResolver(), Settings.Secure.ENABLED_INPUT_METHODS);
+
+            // The placeholder IME should be disabled;
+            assertEquals(ENABLE_IME_SUCCESS, result);
+            assertFalse(enabledIMEs.contains(ImeId));
+        }
+    }
+
     private void assertCanSetAndGetShowModeAndCallbackHappens(
             int mode, InstrumentedAccessibilityService service)
             throws Exception  {
@@ -197,9 +255,14 @@
     }
 
     private class TestImeSession implements AutoCloseable {
-        TestImeSession(String imeId) {
-            // Enable the dummy IME by shell command.
-            final String enableImeCommand = ShellCommandUtils.enableIme(imeId);
+        TestImeSession(String imeId, boolean enabled) {
+            // Enable/disable the placeholder IME by shell command.
+            final String enableImeCommand;
+            if (enabled) {
+                enableImeCommand = ShellCommandUtils.enableIme(imeId);
+            } else {
+                enableImeCommand = ShellCommandUtils.disableIme(imeId);
+            }
             ShellCommandBuilder.create(mInstrumentation)
                     .addCommand(enableImeCommand)
                     .run();
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
index 94791de..4746f01 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -24,6 +24,7 @@
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.supportsMultiDisplay;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
 import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
@@ -44,15 +45,20 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assume.assumeTrue;
 
+import android.Manifest;
 import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.cts.activities.AccessibilityWindowReportingActivity;
 import android.accessibilityservice.cts.activities.NonDefaultDisplayActivity;
+import android.accessibilityservice.cts.activities.NotTouchableWindowTestActivity;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.content.ComponentName;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.platform.test.annotations.AppModeFull;
 import android.view.Gravity;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -68,6 +74,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -76,6 +84,7 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.TimeoutException;
 
@@ -154,10 +163,9 @@
         final WindowManager.LayoutParams paramsForBottom = layoutParmsForWindowOnBottom();
         final Button button = new Button(mActivity);
         button.setText(R.string.button1);
-        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
-                () -> mActivity.getWindowManager().addView(button, paramsForTop)),
-                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
-                TIMEOUT_ASYNC_PROCESSING);
+
+        addWindowAndWaitForEvent(button, paramsForTop,
+                        filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED));
 
         // Move window from top to bottom
         sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
@@ -410,18 +418,112 @@
         assertTrue("Failed to find accessibility window for auto-complete pop-up", foundPopup);
     }
 
+    @AppModeFull
+    @Test
+    public void showNotTouchableWindow_activityWindowIsNotVisible()
+            throws TimeoutException {
+        try {
+            launchNotTouchableWindowTestActivityFromShell();
+
+            Intent intent = new Intent();
+            intent.setAction(NotTouchableWindowTestActivity.ADD_WINDOW);
+
+            try {
+                // Waits for the not-touchable activity is covered by the untrusted window.
+                sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                        () -> sInstrumentation.getContext().sendBroadcast(intent)),
+                        (event) -> {
+                            final AccessibilityWindowInfo notTouchableWindow =
+                                    findWindowByTitle(sUiAutomation,
+                                            NotTouchableWindowTestActivity.TITLE);
+                            return notTouchableWindow == null;
+                        }, TIMEOUT_ASYNC_PROCESSING);
+            } finally {
+                intent.setAction(NotTouchableWindowTestActivity.REMOVE_WINDOW);
+                sendIntentAndWaitForEvent(intent,
+                        filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+            }
+        } finally {
+            Intent intent = new Intent();
+            intent.setAction(NotTouchableWindowTestActivity.FINISH_ACTIVITY);
+            sendIntentAndWaitForEvent(intent,
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+        }
+    }
+
+    @AppModeFull
+    @Test
+    public void showNotTouchableTrustedWindow_activityWindowIsVisible()
+            throws TimeoutException {
+        try {
+            launchNotTouchableWindowTestActivityFromShell();
+
+            Intent intent = new Intent();
+            intent.setAction(NotTouchableWindowTestActivity.ADD_TRUSTED_WINDOW);
+
+            try {
+                SystemUtil.runWithShellPermissionIdentity(sUiAutomation, () -> {
+                    sendIntentAndWaitForEvent(intent,
+                            filterWindowsChangeTypesAndWindowTitle(sUiAutomation,
+                                    WINDOWS_CHANGE_ADDED,
+                                    NotTouchableWindowTestActivity.NON_TOUCHABLE_WINDOW_TITLE.toString())
+                    );
+                }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+
+                List<AccessibilityWindowInfo> windows = sUiAutomation.getWindows();
+                assertNotNull(windows);
+
+                assertEquals(1, windows.stream().filter(
+                        w -> NotTouchableWindowTestActivity.TITLE.equals(w.getTitle())).count());
+            } finally {
+                intent.setAction(NotTouchableWindowTestActivity.REMOVE_WINDOW);
+                sendIntentAndWaitForEvent(intent,
+                        filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+            }
+        } finally {
+            Intent intent = new Intent();
+            intent.setAction(NotTouchableWindowTestActivity.FINISH_ACTIVITY);
+            sendIntentAndWaitForEvent(intent,
+                    filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_REMOVED));
+        }
+    }
+
+    // We want to test WindowState#isTrustedOverlay which refers to flag stored in the
+    // Session class and is not updated since the Session is created.
+    // Use shell command instead of ActivityLaunchUtils to get INTERNAL_SYSTEM_WINDOW
+    // permission when the Session is created.
+    private void launchNotTouchableWindowTestActivityFromShell() {
+        SystemUtil.runWithShellPermissionIdentity(sUiAutomation, () -> {
+            sUiAutomation.executeAndWaitForEvent(
+                    () -> {
+                        final ComponentName componentName = new ComponentName(
+                                sInstrumentation.getContext(), NotTouchableWindowTestActivity.class);
+
+                        String command = "am start -n " + componentName.flattenToString();
+                        try {
+                            SystemUtil.runShellCommand(sInstrumentation, command);
+                        } catch (IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                    },
+                    (event) -> {
+                        final AccessibilityWindowInfo window =
+                                findWindowByTitleAndDisplay(sUiAutomation,
+                                        NotTouchableWindowTestActivity.TITLE, 0);
+                        return window != null;
+                    }, DEFAULT_TIMEOUT_MS);
+        }, Manifest.permission.INTERNAL_SYSTEM_WINDOW);
+    }
+
     private View showTopWindowAndWaitForItToShowUp() throws TimeoutException {
         final WindowManager.LayoutParams paramsForTop = layoutParmsForWindowOnTop();
         final Button button = new Button(mActivity);
         button.setText(R.string.button1);
-        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
-                () -> mActivity.getWindowManager().addView(button, paramsForTop)),
-                (event) -> {
-                    return (event.getEventType() == TYPE_WINDOWS_CHANGED)
-                            && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
-                            && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
-                },
-                TIMEOUT_ASYNC_PROCESSING);
+        addWindowAndWaitForEvent(button, paramsForTop, (event) -> {
+            return (event.getEventType() == TYPE_WINDOWS_CHANGED)
+                    && (findWindowByTitle(sUiAutomation, mActivityTitle) != null)
+                    && (findWindowByTitle(sUiAutomation, TOP_WINDOW_TITLE) != null);
+        });
         return button;
     }
 
@@ -454,4 +556,22 @@
         });
         return params;
     }
+
+    private void addWindowAndWaitForEvent(View view, WindowManager.LayoutParams params,
+            UiAutomation.AccessibilityEventFilter filter)
+            throws TimeoutException {
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> mActivity.getWindowManager().addView(view, params)),
+                filter,
+                TIMEOUT_ASYNC_PROCESSING);
+    }
+
+    private void sendIntentAndWaitForEvent(Intent intent,
+            UiAutomation.AccessibilityEventFilter filter) throws TimeoutException {
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> sInstrumentation.getContext().sendBroadcast(intent)),
+                filter,
+                TIMEOUT_ASYNC_PROCESSING);
+    }
+
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java
new file mode 100644
index 0000000..49142e8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/MagnificationConfigTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
+
+import static org.junit.Assert.assertEquals;
+
+import android.accessibilityservice.MagnificationConfig;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class for testing {@link android.accessibilityservice.MagnificationConfig}.
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MagnificationConfigTest {
+
+    private final int mMode = MAGNIFICATION_MODE_FULLSCREEN;
+    private final float mScale = 1;
+    private final float mCenterX = 2;
+    private final float mCenterY = 3;
+
+    @Test
+    public void testMarshaling() {
+        // Populate the magnification config to marshal.
+        MagnificationConfig magnificationConfig = populateMagnificationConfig();
+
+        // Marshal and unmarshal the magnification config.
+        Parcel parcel = Parcel.obtain();
+        magnificationConfig.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        MagnificationConfig fromParcel =
+                MagnificationConfig.CREATOR.createFromParcel(parcel);
+
+        // Make sure all fields properly marshaled.
+        assertEqualsMagnificationConfig(magnificationConfig, fromParcel);
+
+        parcel.recycle();
+    }
+
+    @Test
+    public void testGetterMethods_dataPopulated_assertDataEqual() {
+        MagnificationConfig magnificationConfig = populateMagnificationConfig();
+
+        assertEquals("getMode is different from magnificationConfig", mMode,
+                magnificationConfig.getMode());
+        assertEquals("getScale is different from magnificationConfig", mScale,
+                magnificationConfig.getScale(), 0);
+        assertEquals("getCenterX is different from magnificationConfig", mCenterX,
+                magnificationConfig.getCenterX(), 0);
+        assertEquals("getCenterY is different from magnificationConfig", mCenterY,
+                magnificationConfig.getCenterY(), 0);
+    }
+
+    private void assertEqualsMagnificationConfig(MagnificationConfig expectedConfig,
+            MagnificationConfig actualConfig) {
+        assertEquals("getMode has incorrect value", expectedConfig.getMode(),
+                actualConfig.getMode());
+        assertEquals("getScale has incorrect value", expectedConfig.getScale(),
+                actualConfig.getScale(), 0);
+        assertEquals("getCenterX has incorrect value", expectedConfig.getCenterX(),
+                actualConfig.getCenterX(), 0);
+        assertEquals("getCenterY has incorrect value", expectedConfig.getCenterY(),
+                actualConfig.getCenterY(), 0);
+    }
+
+    private MagnificationConfig populateMagnificationConfig() {
+        MagnificationConfig.Builder builder =
+                new MagnificationConfig.Builder();
+        MagnificationConfig magnificationConfig = builder.setMode(mMode).setScale(
+                mScale).setCenterX(mCenterX).setCenterY(mCenterY).build();
+        return magnificationConfig;
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.java
new file mode 100644
index 0000000..7558758
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/StubInputMethod.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 android.accessibilityservice.cts;
+
+import android.inputmethodservice.InputMethodService;
+
+public class StubInputMethod extends InputMethodService {
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
index 826e53f..808ad88 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorationStubAccessibilityService.java
@@ -16,8 +16,6 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_END;
 import static android.view.accessibility.AccessibilityEvent.TYPE_GESTURE_DETECTION_START;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
 
 import android.view.accessibility.AccessibilityEvent;
 
@@ -40,8 +38,6 @@
                     break;
                 case TYPE_GESTURE_DETECTION_START:
                 case TYPE_GESTURE_DETECTION_END:
-                case TYPE_VIEW_CLICKED:
-                case TYPE_VIEW_LONG_CLICKED:
                     mCollectedEvents.add(event.getEventType());
             }
         }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index 7ffb23d..26f0907 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -43,8 +43,6 @@
 import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
 import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
 import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
-import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_LONG_CLICKED;
 
 import static org.hamcrest.CoreMatchers.both;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -53,16 +51,17 @@
 import android.accessibility.cts.common.InstrumentedAccessibilityService;
 import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
 import android.accessibility.cts.common.ShellCommandBuilder;
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.GestureDescription.StrokeDescription;
 import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.ActivityLaunchUtils;
 import android.accessibilityservice.cts.utils.EventCapturingClickListener;
 import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
 import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
 import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
-import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.PointF;
 import android.graphics.Region;
@@ -82,7 +81,9 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.RuleChain;
@@ -99,14 +100,17 @@
 @AppModeFull
 public class TouchExplorerTest {
     // Constants
-    private static final float GESTURE_LENGTH_MMS = 10.0f;
+    private static final float GESTURE_LENGTH_MMS = 15.0f;
+    private static final float MIN_SCREEN_WIDTH_MM = 40.0f;
+
+    private static String sEnabledServices;
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
     private TouchExplorationStubAccessibilityService mService;
-    private Instrumentation mInstrumentation;
-    private UiAutomation mUiAutomation;
     private boolean mHasTouchscreen;
     private boolean mScreenBigEnough;
     private long mSwipeTimeMillis;
-    private String mEnabledServices;
     private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
     private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
     private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
@@ -114,7 +118,7 @@
             new EventCapturingLongClickListener();
 
     private ActivityTestRule<GestureDispatchActivity> mActivityRule =
-            new ActivityTestRule<>(GestureDispatchActivity.class, false);
+            new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
 
     private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
             mServiceRule =
@@ -132,42 +136,55 @@
     float mSwipeDistance;
     View mView;
 
-    @Before
-    public void setUp() throws Exception {
-        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
         // Save enabled accessibility services before disabling them so they can be re-enabled after
         // the test.
-        mEnabledServices = Settings.Secure.getString(
-                mInstrumentation.getContext().getContentResolver(),
+        sEnabledServices = Settings.Secure.getString(
+                sInstrumentation.getContext().getContentResolver(),
                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
         // Disable all services before enabling Accessibility service to prevent flakiness
         // that depends on which services are enabled.
         InstrumentedAccessibilityService.disableAllServices();
-        PackageManager pm = mInstrumentation.getContext().getPackageManager();
+        sUiAutomation = sInstrumentation.getUiAutomation(
+                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        ShellCommandBuilder.create(sInstrumentation)
+                .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, sEnabledServices)
+                .run();
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        ActivityLaunchUtils.homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+
+        mActivityRule.launchActivity(null);
+        PackageManager pm = sInstrumentation.getContext().getPackageManager();
         mHasTouchscreen =
                 pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
                         || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
         // Find window size, check that it is big enough for gestures.
         // Gestures will start in the center of the window, so we need enough horiz/vert space.
         mService = mServiceRule.enableService();
-        // To prevent a deadlock, we disable UiAutomation while another a11y service is running.
-        mInstrumentation.getUiAutomation(
-                UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES).destroy();
         mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
-        WindowManager windowManager =
-                (WindowManager)
-                        mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+        WindowManager windowManager = sInstrumentation.getContext().getSystemService(
+                WindowManager.class);
         final DisplayMetrics metrics = new DisplayMetrics();
         windowManager.getDefaultDisplay().getRealMetrics(metrics);
         mScreenBigEnough =
-                mView.getWidth() / 2
+                mView.getWidth()
                         > TypedValue.applyDimension(
-                                TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
+                                TypedValue.COMPLEX_UNIT_MM, MIN_SCREEN_WIDTH_MM, metrics);
         if (!mHasTouchscreen || !mScreenBigEnough) return;
 
         mView.setOnHoverListener(mHoverListener);
         mView.setOnTouchListener(mTouchListener);
-        mInstrumentation.runOnMainSync(
+        sInstrumentation.runOnMainSync(
                 () -> {
                     int[] viewLocation = new int[2];
                     mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
@@ -175,8 +192,9 @@
                     final int midY = mView.getHeight() / 2;
                     mView.getLocationOnScreen(viewLocation);
                     mTapLocation = new PointF(viewLocation[0] + midX, viewLocation[1] + midY);
-                    mSwipeDistance = mView.getWidth() / 4;
-
+                    mSwipeDistance =
+                            TypedValue.applyDimension(
+                                    TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MMS, metrics);
                     // This must be slower than 10mm per 150ms to be detected as touch exploration.
                     final double swipeDistanceMm = mSwipeDistance / metrics.xdpi * 25.4;
                     mSwipeTimeMillis = (long) swipeDistanceMm * 20;
@@ -186,14 +204,6 @@
                 });
     }
 
-    @After
-    public void postTestTearDown() {
-        ShellCommandBuilder.create(mInstrumentation)
-                .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mEnabledServices)
-                .run();
-
-    }
-
     /** Test a slow swipe which should initiate touch exploration. */
     @Test
     @AppModeFull
@@ -282,7 +292,7 @@
     public void testSloppyDoubleTapAccessibilityFocus_performsClick() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
         syncAccessibilityFocusToInputFocus();
-        int slop = ViewConfiguration.get(mInstrumentation.getContext()).getScaledDoubleTapSlop();
+        int slop = ViewConfiguration.get(sInstrumentation.getContext()).getScaledDoubleTapSlop();
         dispatch(multiTap(mTapLocation, 2, slop));
         mHoverListener.assertNonePropagated();
         // The click should not be delivered via touch events in this case.
@@ -290,8 +300,7 @@
         mService.assertPropagated(
                 TYPE_VIEW_ACCESSIBILITY_FOCUSED,
                 TYPE_TOUCH_INTERACTION_START,
-                TYPE_TOUCH_INTERACTION_END,
-                TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_END);
         mClickListener.assertClicked(mView);
     }
 
@@ -313,8 +322,7 @@
         mService.assertPropagated(
                 TYPE_VIEW_ACCESSIBILITY_FOCUSED,
                 TYPE_TOUCH_INTERACTION_START,
-                TYPE_TOUCH_INTERACTION_END,
-                TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_END);
         mClickListener.assertClicked(mView);
     }
 
@@ -378,8 +386,7 @@
                 TYPE_TOUCH_INTERACTION_START,
                 TYPE_TOUCH_EXPLORATION_GESTURE_START,
                 TYPE_TOUCH_EXPLORATION_GESTURE_END,
-                TYPE_TOUCH_INTERACTION_END,
-                TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_END);
         mClickListener.assertClicked(mView);
     }
 
@@ -391,6 +398,7 @@
     @AppModeFull
     public void testDoubleTapNoAccessibilityFocus_sendsTouchEvents() {
         if (!mHasTouchscreen || !mScreenBigEnough) return;
+
         // Do a single tap so there is a valid last touch-explored location.
         dispatch(click(mTapLocation));
         mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
@@ -407,7 +415,7 @@
         // The click gets delivered as a series of touch events.
         mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
         mService.assertPropagated(
-                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mClickListener.assertClicked(mView);
     }
 
@@ -436,7 +444,7 @@
         // The click gets delivered as a series of touch events.
         mTouchListener.assertPropagated(ACTION_DOWN, ACTION_UP);
         mService.assertPropagated(
-                TYPE_TOUCH_INTERACTION_START, TYPE_VIEW_LONG_CLICKED, TYPE_TOUCH_INTERACTION_END);
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mLongClickListener.assertLongClicked(mView);
     }
 
@@ -463,8 +471,7 @@
         mService.assertPropagated(
                 TYPE_VIEW_ACCESSIBILITY_FOCUSED,
                 TYPE_TOUCH_INTERACTION_START,
-                TYPE_TOUCH_INTERACTION_END,
-                TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_END);
         mClickListener.assertClicked(mView);
     }
 
@@ -578,14 +585,14 @@
         mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
         // We still want accessibility events to tell us when the gesture starts and ends.
         mService.assertPropagated(
-                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mService.clearEvents();
         // Swipe starting inside the passthrough region but ending outside of it. This should still
         // behave as a passthrough interaction.
         dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
         mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
         mService.assertPropagated(
-                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mService.clearEvents();
         // Swipe outside the passthrough region. This should not generate touch events.
         dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java
new file mode 100644
index 0000000..9defc25
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchInteractionControllerTest.java
@@ -0,0 +1,433 @@
+/*
+ * 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.accessibilityservice.cts;
+
+import static android.accessibilityservice.cts.utils.AsyncUtils.await;
+import static android.accessibilityservice.cts.utils.GestureUtils.add;
+import static android.accessibilityservice.cts.utils.GestureUtils.click;
+import static android.accessibilityservice.cts.utils.GestureUtils.dispatchGesture;
+import static android.accessibilityservice.cts.utils.GestureUtils.doubleTap;
+import static android.accessibilityservice.cts.utils.GestureUtils.swipe;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_ENTER;
+import static android.view.MotionEvent.ACTION_HOVER_EXIT;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_POINTER_DOWN;
+import static android.view.MotionEvent.ACTION_POINTER_UP;
+import static android.view.MotionEvent.ACTION_UP;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_END;
+import static android.view.accessibility.AccessibilityEvent.TYPE_TOUCH_INTERACTION_START;
+import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
+import android.accessibility.cts.common.InstrumentedAccessibilityService;
+import android.accessibility.cts.common.InstrumentedAccessibilityServiceTestRule;
+import android.accessibility.cts.common.ShellCommandBuilder;
+import android.accessibilityservice.GestureDescription;
+import android.accessibilityservice.GestureDescription.StrokeDescription;
+import android.accessibilityservice.TouchInteractionController;
+import android.accessibilityservice.cts.AccessibilityGestureDispatchTest.GestureDispatchActivity;
+import android.accessibilityservice.cts.utils.ActivityLaunchUtils;
+import android.accessibilityservice.cts.utils.EventCapturingClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingHoverListener;
+import android.accessibilityservice.cts.utils.EventCapturingLongClickListener;
+import android.accessibilityservice.cts.utils.EventCapturingTouchListener;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.pm.PackageManager;
+import android.graphics.PointF;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executors;
+
+/**
+ * A set of tests for testing touch exploration. Each test dispatches a gesture and checks for the
+ * appropriate hover and/or touch events followed by the appropriate accessibility events. Some
+ * tests will then check for events from the view.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+@Presubmit
+public class TouchInteractionControllerTest {
+    // Constants
+    private static final float GESTURE_LENGTH_MM = 15.0f;
+    private static final float MIN_SCREEN_WIDTH_MM = 40.0f;
+
+    private static String sEnabledServices;
+    private static Instrumentation sInstrumentation;
+    private static UiAutomation sUiAutomation;
+
+    private TouchExplorationStubAccessibilityService mService;
+    private boolean mHasTouchscreen;
+    private boolean mScreenBigEnough;
+    private long mSwipeTimeMillis;
+    private EventCapturingHoverListener mHoverListener = new EventCapturingHoverListener(false);
+    private EventCapturingTouchListener mTouchListener = new EventCapturingTouchListener(false);
+    private EventCapturingClickListener mClickListener = new EventCapturingClickListener();
+    private EventCapturingLongClickListener mLongClickListener =
+            new EventCapturingLongClickListener();
+
+    private ActivityTestRule<GestureDispatchActivity> mActivityRule =
+            new ActivityTestRule<>(GestureDispatchActivity.class, false, false);
+
+    private InstrumentedAccessibilityServiceTestRule<TouchExplorationStubAccessibilityService>
+            mServiceRule =
+                    new InstrumentedAccessibilityServiceTestRule<>(
+                            TouchExplorationStubAccessibilityService.class, false);
+
+    private AccessibilityDumpOnFailureRule mDumpOnFailureRule =
+            new AccessibilityDumpOnFailureRule();
+
+    @Rule
+    public final RuleChain mRuleChain =
+            RuleChain.outerRule(mActivityRule).around(mServiceRule).around(mDumpOnFailureRule);
+
+    PointF mTapLocation; // Center of activity. Gestures all start from around this point.
+    float mSwipeDistance;
+    View mView;
+    TouchInteractionController mController;
+
+    @BeforeClass
+    public static void oneTimeSetup() {
+        sInstrumentation = InstrumentationRegistry.getInstrumentation();
+        // Save enabled accessibility services before disabling them so they can be re-enabled after
+        // the test.
+        sEnabledServices =
+                Settings.Secure.getString(
+                        sInstrumentation.getContext().getContentResolver(),
+                        Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
+        // Disable all services before enabling Accessibility service to prevent flakiness
+        // that depends on which services are enabled.
+        InstrumentedAccessibilityService.disableAllServices();
+        sUiAutomation =
+                sInstrumentation.getUiAutomation(
+                        UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
+    }
+
+    @AfterClass
+    public static void postTestTearDown() {
+        ShellCommandBuilder.create(sInstrumentation)
+                .putSecureSetting(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, sEnabledServices)
+                .run();
+        sUiAutomation.destroy();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        ActivityLaunchUtils.homeScreenOrBust(sInstrumentation.getContext(), sUiAutomation);
+
+        mActivityRule.launchActivity(null);
+        PackageManager pm = sInstrumentation.getContext().getPackageManager();
+        mHasTouchscreen =
+                pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)
+                        || pm.hasSystemFeature(PackageManager.FEATURE_FAKETOUCH);
+        // Find window size, check that it is big enough for gestures.
+        // Gestures will start in the center of the window, so we need enough horiz/vert space.
+        mService = mServiceRule.enableService();
+        mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+        WindowManager windowManager =
+                sInstrumentation.getContext().getSystemService(WindowManager.class);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getRealMetrics(metrics);
+        mScreenBigEnough =
+                mView.getWidth()
+                        > TypedValue.applyDimension(
+                                TypedValue.COMPLEX_UNIT_MM, MIN_SCREEN_WIDTH_MM, metrics);
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+
+        mView.setOnHoverListener(mHoverListener);
+        mView.setOnTouchListener(mTouchListener);
+        sInstrumentation.runOnMainSync(
+                () -> {
+                    int[] viewLocation = new int[2];
+                    mView = mActivityRule.getActivity().findViewById(R.id.full_screen_text_view);
+                    final int midX = mView.getWidth() / 2;
+                    final int midY = mView.getHeight() / 2;
+                    mView.getLocationOnScreen(viewLocation);
+                    mTapLocation = new PointF(viewLocation[0] + midX, viewLocation[1] + midY);
+                    mSwipeDistance =
+                            TypedValue.applyDimension(
+                                    TypedValue.COMPLEX_UNIT_MM, GESTURE_LENGTH_MM, metrics);
+                    // This must be slower than 10mm per 150ms to be detected as touch exploration.
+                    final double swipeDistanceMm = mSwipeDistance / metrics.xdpi * 25.4;
+                    mSwipeTimeMillis = (long) swipeDistanceMm * 20;
+
+                    mView.setOnClickListener(mClickListener);
+                    mView.setOnLongClickListener(mLongClickListener);
+                });
+        mController = mService.getTouchInteractionController(Display.DEFAULT_DISPLAY);
+    }
+
+    @After
+    public void tearDown() {
+        mService.disableSelfAndRemove();
+    }
+
+    public void assertBasicConsistency() {
+        assertEquals(Display.DEFAULT_DISPLAY, mController.getDisplayId());
+        assertTrue(mController.getMaxPointerCount() > 0);
+        int state = mController.getState();
+        assertNotEquals("Unknown state: " + state, TouchInteractionController.stateToString(state));
+    }
+
+    /** Test whether we can initiate touch exploration when performing a single tap. */
+    @Test
+    @AppModeFull
+    public void testSingleTap_initiatesTouchExploration() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        mController.registerCallback(
+                Executors.newSingleThreadExecutor(),
+                new BaseCallback() {
+                    public void onMotionEvent(MotionEvent event) {
+                        if (event.getActionMasked() == ACTION_DOWN) {
+                            mController.requestTouchExploration();
+                        }
+                    }
+                });
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+    }
+
+    /** Test whether we can initiate a drag. */
+    @Test
+    @AppModeFull
+    public void testTwoFingerDrag_sendsTouchEvents() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        mController.registerCallback(
+                Executors.newSingleThreadExecutor(),
+                new BaseCallback() {
+                    public void onMotionEvent(MotionEvent event) {
+                        if (event.getActionMasked() == ACTION_POINTER_DOWN) {
+                            mController.requestDragging(event.getPointerId(0));
+                        }
+                    }
+                });
+        // A two point moving that are in the same direction can perform a drag gesture by
+        // TouchExplorer while one point moving can not perform a drag gesture. We use two
+        // swipes
+        // to emulate a two finger drag gesture.
+        final int twoFingerOffset = (int) mSwipeDistance;
+        final PointF dragStart = mTapLocation;
+        final PointF dragEnd = add(dragStart, 0, mSwipeDistance);
+        final PointF finger1Start = add(dragStart, twoFingerOffset, 0);
+        final PointF finger1End = add(finger1Start, 0, mSwipeDistance);
+        final PointF finger2Start = add(dragStart, -twoFingerOffset, 0);
+        final PointF finger2End = add(finger2Start, 0, mSwipeDistance);
+        dispatch(
+                swipe(finger1Start, finger1End, mSwipeTimeMillis),
+                swipe(finger2Start, finger2End, mSwipeTimeMillis));
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+    }
+
+    /**
+     * This method tests the case where two fingers are moving independently. The gesture should be
+     * delegated to the view as-is. This is distinct from dragging, where two fingers are delegated
+     * to the view as one finger.
+     */
+    @Test
+    @AppModeFull
+    public void testTwoFingersMovingIndependently_shouldDelegate() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        mController.registerCallback(
+                Executors.newSingleThreadExecutor(),
+                new BaseCallback() {
+                    public void onMotionEvent(MotionEvent event) {
+                        if (event.getActionMasked() == ACTION_POINTER_DOWN) {
+                            mController.requestDelegating();
+                        }
+                    }
+                });
+        // Move two fingers towards eacher slowly.
+        PointF finger1Start = add(mTapLocation, -mSwipeDistance, 0);
+        PointF finger1End = add(mTapLocation, -10, 0);
+        StrokeDescription swipe1 = swipe(finger1Start, finger1End, mSwipeTimeMillis);
+        PointF finger2Start = add(mTapLocation, mSwipeDistance, 0);
+        PointF finger2End = add(mTapLocation, 10, 0);
+        StrokeDescription swipe2 = swipe(finger2Start, finger2End, mSwipeTimeMillis);
+        dispatch(swipe1, swipe2);
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertPropagated(
+                ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE, ACTION_POINTER_UP, ACTION_UP);
+    }
+
+    /** Insure that double-tap is recognized as a single interaction. */
+    @Test
+    @AppModeFull
+    public void testDoubleTap_producesSingleInteraction() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        dispatch(doubleTap(mTapLocation));
+        mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+    }
+
+    /**
+     * Test the case where we want to click on the item that has accessibility focus by using
+     * AccessibilityNodeInfo.performAction. Note that this test does not request that double tap be
+     * dispatched to the accessibility service, meaning that it will be handled by the framework and
+     * the view will be clicked.
+     */
+    @Test
+    @AppModeFull
+    public void testPerformClickAccessibilityFocus_performsClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        syncAccessibilityFocusToInputFocus();
+        mController.performClick();
+        mService.assertPropagated(TYPE_VIEW_ACCESSIBILITY_FOCUSED);
+        mHoverListener.assertNonePropagated();
+        // The click should not be delivered via touch events in this case.
+        mTouchListener.assertNonePropagated();
+        mClickListener.assertClicked(mView);
+    }
+
+    /**
+     * Test the case where we double tap but there is no accessibility focus. Nothing should happen.
+     */
+    @Test
+    @AppModeFull
+    public void testPerformClickNoFocus_doesNotPerformClick() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        mController.performClick();
+        mHoverListener.assertNonePropagated();
+        mTouchListener.assertNonePropagated();
+        mClickListener.assertNoneClicked();
+    }
+
+    /** Set the accessibility focus to the element that has input focus. */
+    @Test
+    @AppModeFull
+    public void testPerformLongClick_sendsMotionEvents() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        // First perform touch exploration.
+        mController.registerCallback(
+                Executors.newSingleThreadExecutor(),
+                new BaseCallback() {
+                    public void onMotionEvent(MotionEvent event) {
+                        if (event.getActionMasked() == ACTION_DOWN) {
+                            mController.requestTouchExploration();
+                        }
+                    }
+                });
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+        // Wait for the interaction ends before beginning a new one.
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mController.unregisterAllCallbacks();
+        mController.registerCallback(
+                Executors.newSingleThreadExecutor(),
+                new BaseCallback() {
+                    public void onMotionEvent(MotionEvent event) {
+                        if (event.getActionMasked() == ACTION_DOWN) {
+                            mController.performLongClickAndStartDrag();
+                        }
+                    }
+                });
+        PointF endPoint = add(mTapLocation, mSwipeDistance, 0);
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0), mSwipeTimeMillis));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+    }
+
+    @Test
+    @AppModeFull
+    public void testRemove_shouldReturnControlToFramework() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        assertBasicConsistency();
+        TouchInteractionController.Callback callback = new BaseCallback();
+        mController.registerCallback(Executors.newSingleThreadExecutor(), callback);
+        dispatch(click(mTapLocation));
+        // Nothing should happen because the callback is empty.
+        mTouchListener.assertNonePropagated();
+        mController.unregisterCallback(callback);
+        mHoverListener.assertNonePropagated();
+        dispatch(click(mTapLocation));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_EXIT);
+        mTouchListener.assertNonePropagated();
+    }
+
+    private void syncAccessibilityFocusToInputFocus() {
+        mService.runOnServiceSync(
+                () -> {
+                    AccessibilityNodeInfo focus =
+                            mService.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
+                    focus.performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+                    focus.recycle();
+                });
+        mService.waitForAccessibilityFocus();
+    }
+
+    public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
+        GestureDescription.Builder builder =
+                new GestureDescription.Builder().addStroke(firstStroke);
+        for (StrokeDescription stroke : rest) {
+            builder.addStroke(stroke);
+        }
+        dispatch(builder.build());
+    }
+
+    public void dispatch(GestureDescription gesture) {
+        await(dispatchGesture(mService, gesture));
+    }
+
+    class BaseCallback implements TouchInteractionController.Callback {
+
+        @Override
+        public void onMotionEvent(MotionEvent event) {}
+
+        @Override
+        public void onStateChanged(int state) {}
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.java
new file mode 100644
index 0000000..f66a6fc
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/AccessibilityCacheActivity.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.accessibilityservice.cts.activities;
+
+import android.accessibilityservice.cts.R;
+import android.os.Bundle;
+
+public class AccessibilityCacheActivity extends AccessibilityTestActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.accessibility_cache);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java
new file mode 100644
index 0000000..f4ec5b8
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/activities/NotTouchableWindowTestActivity.java
@@ -0,0 +1,127 @@
+/*
+ * 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.accessibilityservice.cts.activities;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+
+public class NotTouchableWindowTestActivity extends AccessibilityTestActivity  {
+    private class CommandReceiver extends BroadcastReceiver {
+        private View rootView;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            WindowManager.LayoutParams params;
+            switch (intent.getAction()) {
+                case ADD_WINDOW:
+                    if (rootView != null) {
+                        throw new IllegalStateException("Window already exists");
+                    }
+                    rootView = new View(context);
+                    params = createDefaultWindowParams();
+                    context.getSystemService(WindowManager.class).addView(rootView, params);
+                    break;
+
+                case REMOVE_WINDOW:
+                    context.getSystemService(WindowManager.class).removeViewImmediate(rootView);
+                    rootView = null;
+                    break;
+
+                case ADD_TRUSTED_WINDOW:
+                    if (rootView != null) {
+                        throw new IllegalStateException("Window already exists");
+                    }
+                    rootView = new Button(context);
+                    params = createDefaultWindowParams();
+                    params.privateFlags |= PRIVATE_FLAG_TRUSTED_OVERLAY;
+                    context.getSystemService(WindowManager.class).addView(rootView, params);
+                    break;
+
+                case FINISH_ACTIVITY:
+                    if (rootView != null) {
+                        throw new IllegalStateException("Window still exists");
+                    }
+                    finish();
+            }
+        }
+
+    }
+
+    private BroadcastReceiver mBroadcastReceiver = new CommandReceiver();
+
+    public static final CharSequence TITLE =
+            "NotTouchableWindowTestActivity";
+    public static final CharSequence NON_TOUCHABLE_WINDOW_TITLE =
+            "android.accessibilityservice.cts.activities.NON_TOUCHABLE_WINDOW_TITLE";
+
+    public static final String ADD_WINDOW =
+            "android.accessibilityservice.cts.ADD_WINDOW";
+    public static final String REMOVE_WINDOW =
+            "android.accessibilityservice.cts.REMOVE_WINDOW";
+    public static final String ADD_TRUSTED_WINDOW =
+            "android.accessibilityservice.cts.ADD_TRUSTED_WINDOW";
+    public static final String FINISH_ACTIVITY =
+            "android.accessibilityservice.cts.FINISH_ACTIVITY";
+
+    // From WindowManager.java.
+    public static final int PRIVATE_FLAG_TRUSTED_OVERLAY = 0x20000000;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
+        filter.addAction(ADD_WINDOW);
+        filter.addAction(REMOVE_WINDOW);
+        filter.addAction(ADD_TRUSTED_WINDOW);
+        filter.addAction(FINISH_ACTIVITY);
+        this.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
+
+        setTitle(TITLE);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+
+        this.unregisterReceiver(mBroadcastReceiver);
+    }
+
+    private static WindowManager.LayoutParams createDefaultWindowParams() {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.height = WindowManager.LayoutParams.MATCH_PARENT;
+        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
+                | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+        params.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+
+        params.gravity = Gravity.TOP;
+        params.setTitle(NON_TOUCHABLE_WINDOW_TITLE);
+
+        return params;
+    }
+}
\ No newline at end of file
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
index 41c5bad..7e9904c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/ActivityLaunchUtils.java
@@ -63,7 +63,7 @@
     private static final String LOG_TAG = "ActivityLaunchUtils";
     private static final String AM_START_HOME_ACTIVITY_COMMAND =
             "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
-    public static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND =
+    private static final String AM_BROADCAST_CLOSE_SYSTEM_DIALOG_COMMAND =
             "am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS";
 
     // Using a static variable so it can be used in lambdas. Not preserving state in it.
@@ -135,6 +135,11 @@
         wakeUpOrBust(context, uiAutomation);
         if (context.getPackageManager().isInstantApp()) return;
         if (isHomeScreenShowing(context, uiAutomation)) return;
+        final AccessibilityServiceInfo serviceInfo = uiAutomation.getServiceInfo();
+        final int enabledFlags = serviceInfo.flags;
+        // Make sure we could query windows.
+        serviceInfo.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
+        uiAutomation.setServiceInfo(serviceInfo);
         try {
             executeAndWaitOn(
                     uiAutomation,
@@ -159,6 +164,9 @@
             }
 
             fail("Unable to reach home screen");
+        } finally {
+            serviceInfo.flags = enabledFlags;
+            uiAutomation.setServiceInfo(serviceInfo);
         }
     }
 
@@ -182,7 +190,8 @@
                 if (packageName != null) {
                     for (ResolveInfo resolveInfo : resolveInfos) {
                         if ((resolveInfo.activityInfo != null)
-                                && packageName.equals(resolveInfo.activityInfo.packageName)) {
+                                && packageName.equals(resolveInfo.activityInfo.packageName)
+                                && window.isActive()) {
                             return true;
                         }
                     }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
index 87d46ba..c0ce0d2 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingHoverListener.java
@@ -17,14 +17,17 @@
 package android.accessibilityservice.cts.utils;
 
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
-import static java.util.concurrent.TimeUnit.SECONDS;
+
 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 java.util.concurrent.TimeUnit.SECONDS;
+
 import android.view.MotionEvent;
 import android.view.View;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.BlockingQueue;
@@ -95,6 +98,9 @@
                         received.add(MotionEvent.actionToString(action));
                     }
                 }
+                if (expected.size() == received.size()) {
+                    break;
+                }
                 ev = mEvents.poll(waitTime, SECONDS);
             }
             assertEquals(expected, received);
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
index 3997ebe..d435d4c 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/utils/EventCapturingTouchListener.java
@@ -18,14 +18,14 @@
 
 import static android.view.MotionEvent.ACTION_MOVE;
 
-import static java.util.concurrent.TimeUnit.SECONDS;
-
 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 static org.junit.Assert.fail;
 
+import static java.util.concurrent.TimeUnit.SECONDS;
+
 import android.view.MotionEvent;
 import android.view.View;
 
@@ -98,6 +98,9 @@
                         received.add(MotionEvent.actionToString(action));
                     }
                 }
+                if (expected.size() == received.size()) {
+                    break;
+                }
                 ev = events.poll(WAIT_TIME_SECONDS, SECONDS);
             }
             assertEquals(expected, received);
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index 3e1eb6a..96e92ab 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -25,9 +25,11 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
     <uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE" />
     <uses-permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION" />
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index b43053c..e8857cc 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -33,7 +33,7 @@
         <option name="test-file-name" value="CtsBadProviderStubs.apk" />
         <option name="test-file-name" value="CtsCantSaveState1.apk" />
         <option name="test-file-name" value="CtsCantSaveState2.apk" />
-        <option name="test-file-name" value="NotificationDelegator.apk" />
+        <option name="test-file-name" value="NotificationApp.apk" />
         <option name="test-file-name" value="NotificationProvider.apk" />
         <option name="test-file-name" value="NotificationListener.apk" />
         <option name="test-file-name" value="StorageDelegator.apk" />
diff --git a/tests/app/NotificationApp/Android.bp b/tests/app/NotificationApp/Android.bp
new file mode 100644
index 0000000..c5deb32
--- /dev/null
+++ b/tests/app/NotificationApp/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "NotificationApp",
+    defaults: ["cts_support_defaults"],
+    srcs: ["**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "sts",
+    ],
+    sdk_version: "current",
+}
diff --git a/tests/app/NotificationApp/AndroidManifest.xml b/tests/app/NotificationApp/AndroidManifest.xml
new file mode 100644
index 0000000..144a1bb
--- /dev/null
+++ b/tests/app/NotificationApp/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="com.android.test.notificationapp">
+    <!-- for calling matchesCallFilter -->
+    <uses-permission android:name="android.permission.READ_CONTACTS"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+
+    <application android:label="Notification Test App">
+        <activity android:name="com.android.test.notificationapp.NotificationDelegator"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="com.android.test.notificationapp.NotificationRevoker"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="com.android.test.notificationapp.NotificationDelegateAndPost"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <activity
+            android:name="com.android.test.notificationapp.MatchesCallFilterTestActivity"
+            android:excludeFromRecents="true"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <service android:name="com.android.test.notificationapp.TestNotificationListener"
+                 android:exported="true"
+                 android:label="TestNotificationListener"
+                 android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
+            <intent-filter>
+                <action android:name="android.service.notification.NotificationListenerService"/>
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
diff --git a/tests/app/NotificationDelegator/OWNERS b/tests/app/NotificationApp/OWNERS
similarity index 100%
rename from tests/app/NotificationDelegator/OWNERS
rename to tests/app/NotificationApp/OWNERS
diff --git a/tests/app/NotificationDelegator/res/layout/activity.xml b/tests/app/NotificationApp/res/layout/activity.xml
similarity index 100%
rename from tests/app/NotificationDelegator/res/layout/activity.xml
rename to tests/app/NotificationApp/res/layout/activity.xml
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java
new file mode 100644
index 0000000..2b7a4b1
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/MatchesCallFilterTestActivity.java
@@ -0,0 +1,62 @@
+/*
+ * 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.test.notificationapp;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.ContactsContract;
+
+/**
+ * An activity that's only meant to determine whether NotificationManager#matchesCallFilter
+ * was allowed to run, for permission-checking reasons.
+ * It is not used for functionality tests, as it will only call matchesCallFilter on a
+ * meaningless uri.
+ */
+public class MatchesCallFilterTestActivity extends Activity {
+    private NotificationManager mNotificationManager;
+    private Uri mCallDest;
+
+    // result codes for return
+    private static int sNotPermitted = 0;
+    private static int sPermitted = 1;
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        mNotificationManager = getSystemService(NotificationManager.class);
+        mCallDest = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                Uri.encode("+16175551212"));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        callMatchesCallFilter();
+        finish();
+    }
+
+    private void callMatchesCallFilter() {
+        try {
+            mNotificationManager.matchesCallFilter(mCallDest);
+            setResult(sPermitted);
+        } catch (SecurityException e) {
+            setResult(sNotPermitted);
+        }
+    }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java
new file mode 100644
index 0000000..4d52bd9
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegateAndPost.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegateAndPost extends Activity {
+    private static final String TAG = "DelegateAndPost";
+    private static final String DELEGATE = "android.app.stubs";
+    private static final String CHANNEL = "channel";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+
+        nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+        nm.setNotificationDelegate(DELEGATE);
+        Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+
+        Log.d(TAG, "Posting notification with id 9");
+
+        Notification n = new Notification.Builder(this, CHANNEL)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setContentTitle("posted by delegator")
+                .build();
+
+        nm.notify(9, n);
+
+        finish();
+    }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java
new file mode 100644
index 0000000..9745827
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationDelegator.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+
+import android.app.Activity;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationDelegator extends Activity {
+    private static final String TAG = "Delegator";
+    private static final String DELEGATE = "android.app.stubs";
+    private static final String CHANNEL = "channel";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+
+        nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
+        nm.setNotificationDelegate(DELEGATE);
+        Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
+        finish();
+    }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java
new file mode 100644
index 0000000..54c6c8d
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/NotificationRevoker.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.notificationapp;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import android.util.Log;
+
+public class NotificationRevoker extends Activity {
+    private static final String TAG = "Revoker";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity);
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        nm.setNotificationDelegate(null);
+        Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
+        nm.cancelAll();
+        finish();
+    }
+}
diff --git a/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.java b/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.java
new file mode 100644
index 0000000..b294e48
--- /dev/null
+++ b/tests/app/NotificationApp/src/com/android/test/notificationapp/TestNotificationListener.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.test.notificationapp;
+
+import android.service.notification.NotificationListenerService;
+
+// Minimal possible NotificationListenerService, used just to grant notification listener access.
+public class TestNotificationListener extends NotificationListenerService {
+    public static final String TAG = "TestNotificationListener";
+    public static final String PKG = "com.android.test.notificationapp";
+}
diff --git a/tests/app/NotificationDelegator/Android.bp b/tests/app/NotificationDelegator/Android.bp
deleted file mode 100644
index f4fbdd3..0000000
--- a/tests/app/NotificationDelegator/Android.bp
+++ /dev/null
@@ -1,30 +0,0 @@
-// Copyright (C) 2018 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test_helper_app {
-    name: "NotificationDelegator",
-    defaults: ["cts_support_defaults"],
-    srcs: ["**/*.java"],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-        "sts",
-    ],
-    sdk_version: "current",
-}
diff --git a/tests/app/NotificationDelegator/AndroidManifest.xml b/tests/app/NotificationDelegator/AndroidManifest.xml
deleted file mode 100644
index a05dcd2..0000000
--- a/tests/app/NotificationDelegator/AndroidManifest.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2018 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-     package="com.android.test.notificationdelegator">
-    <application android:label="Notification Delegator">
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegator"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="com.android.test.notificationdelegator.NotificationRevoker"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-        <activity android:name="com.android.test.notificationdelegator.NotificationDelegateAndPost"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.DEFAULT"/>
-            </intent-filter>
-        </activity>
-
-    </application>
-</manifest>
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
deleted file mode 100644
index 521adc5..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegateAndPost.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationDelegateAndPost extends Activity {
-    private static final String TAG = "DelegateAndPost";
-    private static final String DELEGATE = "android.app.stubs";
-    private static final String CHANNEL = "channel";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity);
-
-        NotificationManager nm = getSystemService(NotificationManager.class);
-
-        nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
-        nm.setNotificationDelegate(DELEGATE);
-        Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
-
-        Log.d(TAG, "Posting notification with id 9");
-
-        Notification n = new Notification.Builder(this, CHANNEL)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon)
-                .setContentTitle("posted by delegator")
-                .build();
-
-        nm.notify(9, n);
-
-        finish();
-    }
-}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
deleted file mode 100644
index 15cf42d..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationDelegator.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-
-import android.app.Activity;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationDelegator extends Activity {
-    private static final String TAG = "Delegator";
-    private static final String DELEGATE = "android.app.stubs";
-    private static final String CHANNEL = "channel";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity);
-
-        NotificationManager nm = getSystemService(NotificationManager.class);
-
-        nm.createNotificationChannel(new NotificationChannel(CHANNEL, CHANNEL, IMPORTANCE_LOW));
-        nm.setNotificationDelegate(DELEGATE);
-        Log.d(TAG, "Set delegate: " + nm.getNotificationDelegate());
-        finish();
-    }
-}
diff --git a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java b/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
deleted file mode 100644
index 750549a..0000000
--- a/tests/app/NotificationDelegator/src/com/android/test/notificationdelegator/NotificationRevoker.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.test.notificationdelegator;
-
-import android.app.Activity;
-import android.app.NotificationManager;
-import android.os.Bundle;
-import android.util.Log;
-
-public class NotificationRevoker extends Activity {
-    private static final String TAG = "Revoker";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity);
-
-        NotificationManager nm = getSystemService(NotificationManager.class);
-        nm.setNotificationDelegate(null);
-        Log.d(TAG, "Removed delegate: " + nm.getNotificationDelegate());
-        nm.cancelAll();
-        finish();
-    }
-}
diff --git a/tests/app/NotificationProvider/AndroidManifest.xml b/tests/app/NotificationProvider/AndroidManifest.xml
index 09ae4b0..4c6c972 100644
--- a/tests/app/NotificationProvider/AndroidManifest.xml
+++ b/tests/app/NotificationProvider/AndroidManifest.xml
@@ -16,6 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="com.android.test.notificationprovider">
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <application android:label="Notification Provider">
         <activity android:name=".RichNotificationActivity" android:exported="true">
             <intent-filter>
diff --git a/tests/app/NotificationTrampolineBase/AndroidManifest.xml b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
index 093495f..418872f 100644
--- a/tests/app/NotificationTrampolineBase/AndroidManifest.xml
+++ b/tests/app/NotificationTrampolineBase/AndroidManifest.xml
@@ -16,6 +16,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.test.notificationtrampoline">
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <application>
         <service
             android:name=".NotificationTrampolineTestService"
diff --git a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
index e230eb6..120186b 100644
--- a/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
+++ b/tests/app/NotificationTrampolineBase/src/com/android/test/notificationtrampoline/NotificationTrampolineTestService.java
@@ -116,7 +116,8 @@
                             startTargetActivity();
                         }
                     };
-                    registerReceiver(mReceiver, new IntentFilter(mReceiverAction));
+                    registerReceiver(mReceiver, new IntentFilter(mReceiverAction),
+                            Context.RECEIVER_EXPORTED_UNAUDITED);
                     Intent intent = new Intent(mReceiverAction);
                     postNotification(notificationId,
                             PendingIntent.getBroadcast(context, 0, intent, PI_FLAGS));
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 737dfaa..e8a327b 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -65,6 +65,7 @@
     <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application android:label="Android TestCase"
          android:icon="@drawable/size_48x48"
@@ -366,7 +367,6 @@
 
         <activity android:name="android.app.stubs.ActivityManagerStubCrashActivity"
              android:label="ActivityManagerStubCrashActivity"
-             android:multiprocess="true"
              android:process=":ActivityManagerStubCrashActivity"
              android:exported="true">
             <intent-filter>
@@ -477,16 +477,14 @@
             </intent-filter>
         </service>
 
-        <service android:name="android.app.stubs.ToggleableTestTileService"
-             android:exported="true"
-             android:label="BooleanTestTileService"
-             android:icon="@drawable/robot"
-             android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+        <service android:name="android.app.stubs.NotExportedTestTileService"
+                 android:exported="false"
+                 android:label="NotExportedTestTileService"
+                 android:icon="@drawable/robot"
+                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
             <intent-filter>
                 <action android:name="android.service.quicksettings.action.QS_TILE"/>
             </intent-filter>
-            <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
-                 android:value="true"/>
         </service>
 
         <activity android:name="android.app.stubs.AutomaticZenRuleActivity"
diff --git a/tests/app/app/src/android/app/stubs/NotExportedTestTileService.java b/tests/app/app/src/android/app/stubs/NotExportedTestTileService.java
new file mode 100644
index 0000000..f0ac974
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/NotExportedTestTileService.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.app.stubs;
+
+import android.content.ComponentName;
+import android.service.quicksettings.TileService;
+
+public class NotExportedTestTileService extends TileService {
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(NotExportedTestTileService.class.getPackage().getName(),
+                NotExportedTestTileService.class.getName());
+    }
+}
diff --git a/tests/app/app/src/android/app/stubs/TestTileService.java b/tests/app/app/src/android/app/stubs/TestTileService.java
index 623625b..c10ef8f 100644
--- a/tests/app/app/src/android/app/stubs/TestTileService.java
+++ b/tests/app/app/src/android/app/stubs/TestTileService.java
@@ -19,85 +19,10 @@
 import android.content.ComponentName;
 import android.service.quicksettings.TileService;
 
-import java.util.concurrent.atomic.AtomicBoolean;
-
 public class TestTileService extends TileService {
 
-    public static final String TAG = "TestTileService";
-    public static final String PKG = "android.app.stubs";
-    public static final int ICON_ID = R.drawable.robot;
-
-    private static TestTileService sTestTileService = null;
-    AtomicBoolean isConnected = new AtomicBoolean(false);
-    AtomicBoolean isListening = new AtomicBoolean(false);
-    AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
-
-    public static String getId() {
-        return String.format("%s/%s", TestTileService.class.getPackage().getName(),
-                TestTileService.class.getName());
-    }
-
     public static ComponentName getComponentName() {
         return new ComponentName(TestTileService.class.getPackage().getName(),
                 TestTileService.class.getName());
     }
-
-    @Override
-    public void onCreate() {
-        super.onCreate();
-    }
-
-    public static TestTileService getInstance() {
-        return TestTileService.sTestTileService;
-    }
-
-    public void setInstance(TestTileService tile) {
-        sTestTileService = tile;
-    }
-
-    public static boolean isConnected() {
-        return getInstance() != null && getInstance().isConnected.get();
-    }
-
-    public static boolean isListening() {
-        return getInstance().isListening.get();
-    }
-
-    public static boolean hasBeenClicked() {
-        return getInstance().hasBeenClicked.get();
-    }
-
-    @Override
-    public void onStartListening() {
-        super.onStartListening();
-        isListening.set(true);
-    }
-
-    @Override
-    public void onStopListening() {
-        super.onStopListening();
-        isListening.set(false);
-    }
-
-    @Override
-    public void onClick() {
-        super.onClick();
-        hasBeenClicked.set(true);
-    }
-
-    @Override
-    public void onTileAdded() {
-        super.onTileAdded();
-        setInstance(this);
-        isConnected.set(true);
-    }
-
-    @Override
-    public void onTileRemoved() {
-        super.onTileRemoved();
-        setInstance(null);
-        isConnected.set(false);
-        isListening.set(false);
-        hasBeenClicked.set(false);
-    }
 }
diff --git a/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java b/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java
deleted file mode 100644
index 9e91076..0000000
--- a/tests/app/app/src/android/app/stubs/ToggleableTestTileService.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.stubs;
-
-import android.content.ComponentName;
-import android.service.quicksettings.Tile;
-
-public class ToggleableTestTileService extends TestTileService {
-    public static final String TAG = "ToggleableTestTileService";
-    public static final String PKG = "android.app.stubs";
-    public static final int ICON_ID = R.drawable.robot;
-
-    private static TestTileService sTestTileService = null;
-
-    public static boolean isConnected() {
-        return getInstance() != null && getInstance().isConnected.get();
-    }
-
-    public static boolean isListening() {
-        return getInstance().isListening.get();
-    }
-
-    public static TestTileService getInstance() {
-        return ToggleableTestTileService.sTestTileService;
-    }
-
-    @Override
-    public void setInstance(TestTileService tile) {
-        sTestTileService = tile;
-    }
-
-    public static String getId() {
-        return String.format("%s/%s", ToggleableTestTileService.class.getPackage().getName(),
-                ToggleableTestTileService.class.getName());
-    }
-
-    public static ComponentName getComponentName() {
-        return new ComponentName(ToggleableTestTileService.class.getPackage().getName(),
-                ToggleableTestTileService.class.getName());
-    }
-
-    public void toggleState() {
-        if (isListening()) {
-            Tile tile = getInstance().getQsTile();
-            switch(tile.getState()) {
-                case Tile.STATE_ACTIVE:
-                    tile.setState(Tile.STATE_INACTIVE);
-                    break;
-                case Tile.STATE_INACTIVE:
-                    tile.setState(Tile.STATE_ACTIVE);
-                    break;
-                default:
-                    break;
-            }
-            tile.updateTile();
-        }
-    }
-}
diff --git a/tests/app/shared/AndroidManifest.xml b/tests/app/shared/AndroidManifest.xml
index 247a2a8..2eea680 100644
--- a/tests/app/shared/AndroidManifest.xml
+++ b/tests/app/shared/AndroidManifest.xml
@@ -17,6 +17,7 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.app.stubs.shared">
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <application>
         <service
             android:name="android.app.stubs.shared.CloseSystemDialogsTestService"
diff --git a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
index 10daf75..a8b6075 100644
--- a/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerFgsBgStartTest.java
@@ -61,6 +61,7 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -78,10 +79,10 @@
 public class ActivityManagerFgsBgStartTest {
     private static final String TAG = ActivityManagerFgsBgStartTest.class.getName();
 
-    private static final String STUB_PACKAGE_NAME = "android.app.stubs";
-    private static final String PACKAGE_NAME_APP1 = "com.android.app1";
-    private static final String PACKAGE_NAME_APP2 = "com.android.app2";
-    private static final String PACKAGE_NAME_APP3 = "com.android.app3";
+    static final String STUB_PACKAGE_NAME = "android.app.stubs";
+    static final String PACKAGE_NAME_APP1 = "com.android.app1";
+    static final String PACKAGE_NAME_APP2 = "com.android.app2";
+    static final String PACKAGE_NAME_APP3 = "com.android.app3";
 
     private static final String KEY_DEFAULT_FGS_STARTS_RESTRICTION_ENABLED =
             "default_fgs_starts_restriction_enabled";
@@ -99,7 +100,7 @@
             | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
             | PROCESS_CAPABILITY_NETWORK);
 
-    private static final int WAITFOR_MSEC = 10000;
+    static final int WAITFOR_MSEC = 10000;
 
     private static final int TEMP_ALLOWLIST_DURATION_MS = 2000;
 
@@ -1809,6 +1810,133 @@
         }
     }
 
+    @Test
+    public void testFgsStartInBackgroundRestrictions() throws Exception {
+        ApplicationInfo app1Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        ApplicationInfo app2Info = mContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP2, 0);
+        WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        WatchUidRunner uid2Watcher = new WatchUidRunner(mInstrumentation, app2Info.uid,
+                WAITFOR_MSEC);
+        WaitForBroadcast waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+        final String dumpCommand = "dumpsys activity services " + PACKAGE_NAME_APP2
+                + "/android.app.stubs.LocalForegroundService";
+        final long shortWaitMsec = 5_000;
+        try {
+            // Enable the FGS background startForeground() restriction.
+            enableFgsRestriction(true, true, null);
+
+            // Set background restriction for APP2
+            setAppOp(PACKAGE_NAME_APP2, "RUN_ANY_IN_BACKGROUND", false);
+
+            // Start the APP1 into the TOP state.
+            allowBgActivityStart(PACKAGE_NAME_APP1, true);
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_TOP);
+
+            // APP1 binds to APP2.
+            CommandReceiver.sendCommand(mContext, CommandReceiver.COMMAND_BIND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, Context.BIND_INCLUDE_CAPABILITIES, null);
+
+            // APP2 gets proc state BOUND_TOP.
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_BOUND_TOP);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+
+            // START FGS in APP2.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+            waiter.doWait(WAITFOR_MSEC);
+
+            SystemClock.sleep(shortWaitMsec);
+
+            String[] dumpLines = CtsAppTestUtils.executeShellCmd(
+                    mInstrumentation, dumpCommand).split("\n");
+            assertNotNull(findLine(dumpLines, "isForeground=true"));
+
+            // Finish the activity in APP1
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_STOP_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            mInstrumentation.getUiAutomation().performGlobalAction(
+                    AccessibilityService.GLOBAL_ACTION_HOME);
+
+            // APP1 should have been cached state now.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_CACHED_EMPTY);
+
+            // Th FGS in APP2 should have been normal service state now.
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_SERVICE);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+
+            // START FGS in APP1
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE);
+            waiter.doWait(WAITFOR_MSEC);
+
+            // APP2 should be in FGS state too now.
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_FG_SERVICE);
+
+            waiter = new WaitForBroadcast(mInstrumentation.getTargetContext());
+            waiter.prepare(ACTION_START_FGS_RESULT);
+
+            // START FGS in APP2.
+            CommandReceiver.sendCommand(mContext,
+                    CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                    PACKAGE_NAME_APP2, PACKAGE_NAME_APP2, 0, null);
+            waiter.doWait(WAITFOR_MSEC);
+
+            SystemClock.sleep(shortWaitMsec);
+
+            dumpLines = CtsAppTestUtils.executeShellCmd(
+                    mInstrumentation, dumpCommand).split("\n");
+            assertNotNull(findLine(dumpLines, "isForeground=true"));
+
+            // Set background restriction for APP1.
+            setAppOp(PACKAGE_NAME_APP1, "RUN_ANY_IN_BACKGROUND", false);
+
+            // Both of them should have normal service state now.
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_SERVICE);
+            uid2Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_SERVICE);
+        } finally {
+            uid1Watcher.finish();
+            uid2Watcher.finish();
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "appops reset " + PACKAGE_NAME_APP1);
+            CtsAppTestUtils.executeShellCmd(mInstrumentation,
+                    "appops reset " + PACKAGE_NAME_APP2);
+        }
+    }
+
+    /**
+     * Find a line containing {@code label} in {@code lines}.
+     */
+    private String findLine(String[] lines, CharSequence label) {
+        for (String line: lines) {
+            if (line.contains(label)) {
+                return line;
+            }
+        }
+        return null;
+    }
+
     /**
      * When PowerExemptionManager.addToTemporaryAllowList() is called more than one time, the second
      * call can extend the duration of the first call if the first call has not expired yet.
@@ -1964,6 +2092,8 @@
             try {
                 defaultBehavior = Integer.parseInt(defaultBehaviorStr);
             } catch (NumberFormatException e) {
+                Log.e("ActivityManagerFgsBgStartTest",
+                        "getPushMessagingOverQuotaBehavior:", e);
             }
         }
         return defaultBehavior;
diff --git a/tests/app/src/android/app/cts/ActivityManagerTest.java b/tests/app/src/android/app/cts/ActivityManagerTest.java
index 770d305..d5de40a 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -500,6 +500,138 @@
     public void testGetProcessInErrorState() throws Exception {
         List<ActivityManager.ProcessErrorStateInfo> errList = null;
         errList = mActivityManager.getProcessesInErrorState();
+        assertNull(errList);
+
+        // Setup the ANR monitor.
+        final AmMonitor monitor = new AmMonitor(mInstrumentation, null);
+        final ApplicationInfo app1Info = mTargetContext.getPackageManager().getApplicationInfo(
+                PACKAGE_NAME_APP1, 0);
+        final ApplicationInfo stubInfo = mTargetContext.getPackageManager().getApplicationInfo(
+                STUB_PACKAGE_NAME, 0);
+        final WatchUidRunner uid1Watcher = new WatchUidRunner(mInstrumentation, app1Info.uid,
+                WAITFOR_MSEC);
+        final String crashActivityName = "ActivityManagerStubCrashActivity";
+
+        final SettingsSession<Integer> showOnFirstCrash = new SettingsSession<>(
+                Settings.Global.getUriFor(Settings.Global.SHOW_FIRST_CRASH_DIALOG),
+                Settings.Global::getInt, Settings.Global::putInt);
+        final SettingsSession<Integer> showBackground = new SettingsSession<>(
+                Settings.Secure.getUriFor(Settings.Secure.ANR_SHOW_BACKGROUND),
+                Settings.Secure::getInt, Settings.Secure::putInt);
+        try {
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                showOnFirstCrash.set(1);
+                showBackground.set(1);
+            });
+
+            CommandReceiver.sendCommand(mTargetContext,
+                    CommandReceiver.COMMAND_START_ACTIVITY,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+            uid1Watcher.waitFor(WatchUidRunner.CMD_PROCSTATE,
+                    WatchUidRunner.STATE_TOP,
+                    new Integer(ActivityManager.PROCESS_CAPABILITY_ALL));
+
+            // Sleep a while to let things go through.
+            Thread.sleep(WAIT_TIME);
+
+            // Now tell it goto ANR.
+            CommandReceiver.sendCommand(mTargetContext, CommandReceiver.COMMAND_SELF_INDUCED_ANR,
+                    PACKAGE_NAME_APP1, PACKAGE_NAME_APP1, 0, null);
+
+            // Verify we got the ANR.
+            assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_EARLY_ANR, WAITFOR_MSEC));
+
+            // Let it continue.
+            monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+            // Now it should've reached the normal ANR process.
+            assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_ANR, WAITFOR_MSEC * 3));
+
+            // Continue again, we need to see the ANR dialog in order to get the error report.
+            monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+            // Sleep a while to let things go through.
+            Thread.sleep(WAIT_TIME);
+
+            // We shouldn't be able to read the error state info of that.
+            errList = mActivityManager.getProcessesInErrorState();
+            assertNull(errList);
+
+            // Shell should have the access.
+            final List<ActivityManager.ProcessErrorStateInfo>[] holder = new List[1];
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                holder[0] = mActivityManager.getProcessesInErrorState();
+            });
+            assertNotNull(holder[0]);
+            assertEquals(1, holder[0].size());
+            verifyProcessErrorStateInfo(holder[0].get(0),
+                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+                    app1Info.uid,
+                    PACKAGE_NAME_APP1);
+
+            // Start a crashing activity in remote process with the same UID.
+            final Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClassName(STUB_PACKAGE_NAME, STUB_PACKAGE_NAME + "." + crashActivityName);
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mTargetContext.startActivity(intent);
+
+            // Wait for the crash.
+            assertTrue(monitor.waitFor(AmMonitor.WAIT_FOR_CRASHED, WAITFOR_MSEC));
+
+            // Let it continue, we need to see the crash dialog in order to get the error report.
+            monitor.sendCommand(AmMonitor.CMD_CONTINUE);
+
+            // Sleep a while to let things go through.
+            Thread.sleep(WAIT_TIME);
+
+            // We should be able to see this crash info.
+            errList = mActivityManager.getProcessesInErrorState();
+            assertNotNull(errList);
+            assertEquals(1, errList.size());
+
+            verifyProcessErrorStateInfo(errList.get(0),
+                    ActivityManager.ProcessErrorStateInfo.CRASHED,
+                    stubInfo.uid,
+                    STUB_PACKAGE_NAME + ":" + crashActivityName);
+
+            // Shell should have the access to all of the crash info here.
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                holder[0] = mActivityManager.getProcessesInErrorState();
+            });
+            assertNotNull(holder[0]);
+            assertEquals(2, holder[0].size());
+            // The return result is not sorted.
+            final ActivityManager.ProcessErrorStateInfo t0 = holder[0].get(0);
+            final ActivityManager.ProcessErrorStateInfo t1 = holder[0].get(1);
+            final ActivityManager.ProcessErrorStateInfo info0 = t0.uid == stubInfo.uid ? t0 : t1;
+            final ActivityManager.ProcessErrorStateInfo info1 = t1.uid == app1Info.uid ? t1 : t0;
+
+            verifyProcessErrorStateInfo(info0,
+                    ActivityManager.ProcessErrorStateInfo.CRASHED,
+                    stubInfo.uid,
+                    STUB_PACKAGE_NAME + ":" + crashActivityName);
+            verifyProcessErrorStateInfo(info1,
+                    ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING,
+                    app1Info.uid,
+                    PACKAGE_NAME_APP1);
+        } finally {
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                showOnFirstCrash.close();
+                showBackground.close();
+            });
+            monitor.finish();
+            uid1Watcher.finish();
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                mActivityManager.forceStopPackage(PACKAGE_NAME_APP1);
+            });
+        }
+    }
+
+    private void verifyProcessErrorStateInfo(ActivityManager.ProcessErrorStateInfo info,
+            int condition, int uid, String processName) throws Exception {
+        assertEquals(condition, info.condition);
+        assertEquals(uid, info.uid);
+        assertEquals(processName, info.processName);
     }
 
     public void testGetDeviceConfigurationInfo() {
diff --git a/tests/app/src/android/app/cts/ActivityOptionsTest.java b/tests/app/src/android/app/cts/ActivityOptionsTest.java
index eab44c5..ad58410 100644
--- a/tests/app/src/android/app/cts/ActivityOptionsTest.java
+++ b/tests/app/src/android/app/cts/ActivityOptionsTest.java
@@ -16,7 +16,10 @@
 
 package android.app.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import android.app.ActivityOptions;
+import android.graphics.Rect;
 import android.os.Bundle;
 import android.test.AndroidTestCase;
 
@@ -28,4 +31,29 @@
 
         assertNotNull(bundle);
     }
+
+    public void testActivityOptionsBundle_fromBundle() {
+        final int displayId = 9;
+        final Rect bounds = new Rect(0, 10, 100, 90);
+        // Construct some options with set values
+        ActivityOptions opts = ActivityOptions.makeBasic();
+        opts.setLaunchDisplayId(displayId);
+        opts.setLockTaskEnabled(true);
+        opts.setLaunchBounds(bounds);
+
+        Bundle optsBundle = opts.toBundle();
+
+        ActivityOptions opts2 = ActivityOptions.fromBundle(optsBundle);
+        assertThat(opts2.getLaunchDisplayId()).isEqualTo(displayId);
+        assertThat(opts2.getLockTaskMode()).isTrue();
+        assertThat(opts2.getLaunchBounds()).isEqualTo(bounds);
+    }
+
+    public void testGetSetPendingIntentBackgroundActivityLaunchAllowed() {
+        ActivityOptions options = ActivityOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
+        assertThat(options.isPendingIntentBackgroundActivityLaunchAllowed()).isTrue();
+        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        assertThat(options.isPendingIntentBackgroundActivityLaunchAllowed()).isFalse();
+    }
 }
diff --git a/tests/app/src/android/app/cts/AlertDialogTest.java b/tests/app/src/android/app/cts/AlertDialogTest.java
index 4710ac2..28d1579 100644
--- a/tests/app/src/android/app/cts/AlertDialogTest.java
+++ b/tests/app/src/android/app/cts/AlertDialogTest.java
@@ -35,6 +35,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -75,7 +76,7 @@
         });
 
         PollingCheck.waitFor(mActivity.getDialog()::isShowing);
-        PollingCheck.waitFor(mActivity.getDialog().getWindow().getDecorView()::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity.getDialog().getWindow());
     }
 
     @Test
diff --git a/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java b/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
index 75017c1..6dc1067 100644
--- a/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
+++ b/tests/app/src/android/app/cts/AlertDialog_BuilderCursorTest.java
@@ -36,7 +36,7 @@
 
 import androidx.test.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import java.io.File;
 
@@ -103,7 +103,7 @@
         mInstrumentation = getInstrumentation();
         mContext = getActivity();
 
-        PollingCheck.waitFor(() -> getActivity().hasWindowFocus());
+        WindowUtil.waitForFocus(getActivity());
 
         mListView = null;
         mDialog = null;
diff --git a/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java b/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
index d27d4e8..449529e 100644
--- a/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
+++ b/tests/app/src/android/app/cts/AlertDialog_BuilderTest.java
@@ -53,6 +53,7 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -99,7 +100,7 @@
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         Activity activity = mActivityRule.getActivity();
         mContext = activity;
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
     }
 
     @Test
diff --git a/tests/app/src/android/app/cts/BaseTileServiceTest.java b/tests/app/src/android/app/cts/BaseTileServiceTest.java
deleted file mode 100644
index c3eedb4..0000000
--- a/tests/app/src/android/app/cts/BaseTileServiceTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.cts;
-
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assume.assumeTrue;
-
-import android.app.stubs.TestTileService;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.service.quicksettings.TileService;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.UiDevice;
-import android.support.test.uiautomator.Until;
-import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.SystemUtil;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.runner.RunWith;
-
-import java.io.IOException;
-
-@RunWith(AndroidJUnit4.class)
-public abstract class BaseTileServiceTest {
-
-    protected abstract String getTag();
-    protected abstract String getComponentName();
-    protected abstract TileService getTileServiceInstance();
-    protected abstract void waitForConnected(boolean state) throws InterruptedException;
-    protected abstract void waitForListening(boolean state) throws InterruptedException;
-    protected Context mContext;
-
-    final static String DUMP_COMMAND =
-            "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
-
-    // Time between checks for state we expect.
-    protected static final long CHECK_DELAY = 250;
-    // Number of times to check before failing. This is set so the maximum wait time is about 4s,
-    // as some tests were observed to take around 3s.
-    protected static final long CHECK_RETRIES = 15;
-    // Timeout to wait for launcher
-    protected static final long TIMEOUT = 8000;
-
-    protected TileService mTileService;
-    private Intent homeIntent;
-    private String mLauncherPackage;
-
-    @Before
-    public void setUp() throws Exception {
-        assumeTrue(TileService.isQuickSettingsSupported());
-        mContext = InstrumentationRegistry.getContext();
-        homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_HOME);
-
-        mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
-                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
-
-        // Wait for home
-        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        device.pressHome();
-        device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        expandSettings(false);
-        toggleServiceAccess(getComponentName(), false);
-        waitForConnected(false);
-        assertNull(TestTileService.getInstance());
-    }
-
-    protected void startTileService() throws Exception {
-        toggleServiceAccess(getComponentName(), true);
-        waitForConnected(true); // wait for service to be bound
-        mTileService = getTileServiceInstance();
-        assertNotNull(mTileService);
-    }
-
-    protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
-        String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
-                + componentName;
-
-        executeShellCommand(command);
-    }
-
-    public String executeShellCommand(String command) throws IOException {
-        Log.i(getTag(), "Shell command: " + command);
-        try {
-            return SystemUtil.runShellCommand(getInstrumentation(), command);
-        } catch (IOException e) {
-            //bubble it up
-            Log.e(getTag(), "Error running shell command: " + command);
-            throw new IOException(e);
-        }
-    }
-
-    protected void expandSettings(boolean expand) throws Exception {
-        executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
-        Thread.sleep(600); // wait for animation
-    }
-
-    protected void initializeAndListen() throws Exception {
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
-    }
-
-    /**
-     * Find a line containing {@code label} in {@code lines}.
-     */
-    protected String findLine(String[] lines, CharSequence label) {
-        for (String line: lines) {
-            if (line.contains(label)) {
-                return line;
-            }
-        }
-        return null;
-    }
-}
diff --git a/tests/app/src/android/app/cts/BooleanTileServiceTest.java b/tests/app/src/android/app/cts/BooleanTileServiceTest.java
deleted file mode 100644
index 6ffa763..0000000
--- a/tests/app/src/android/app/cts/BooleanTileServiceTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.cts;
-
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.stubs.ToggleableTestTileService;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-
-import org.junit.Test;
-
-public class BooleanTileServiceTest extends BaseTileServiceTest {
-    private final static String TAG = "BooleanTileServiceTest";
-
-    @Test
-    public void testTileIsBoundAndListening() throws Exception {
-        startTileService();
-        expandSettings(true);
-        waitForListening(true);
-    }
-
-    @Test
-    public void testTileInDumpAndHasBooleanState() throws Exception {
-        initializeAndListen();
-
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
-        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        final String line = findLine(dumpLines, tileLabel);
-        assertNotNull(line);
-        assertTrue(line.trim().startsWith("BooleanState"));
-    }
-
-    @Test
-    public void testTileStartsInactive() throws Exception {
-        initializeAndListen();
-
-        assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
-    }
-
-    @Test
-    public void testValueTracksState() throws Exception {
-        initializeAndListen();
-
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
-        String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        String line = findLine(dumpLines, tileLabel);
-
-        // Tile starts inactive
-        assertTrue(line.contains("value=false"));
-
-        ((ToggleableTestTileService) mTileService).toggleState();
-
-        // Close and open QS to make sure that state is refreshed
-        expandSettings(false);
-        waitForListening(false);
-        expandSettings(true);
-        waitForListening(true);
-
-        assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
-
-        dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        line = findLine(dumpLines, tileLabel);
-
-        assertTrue(line.contains("value=true"));
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected String getComponentName() {
-        return ToggleableTestTileService.getComponentName().flattenToString();
-    }
-
-    @Override
-    protected TileService getTileServiceInstance() {
-        return ToggleableTestTileService.getInstance();
-    }
-
-    /**
-     * Waits for the TileService to be in the expected listening state. If it times out, it fails
-     * the test
-     * @param state desired listening state
-     * @throws InterruptedException
-     */
-    @Override
-    protected void waitForListening(boolean state) throws InterruptedException {
-        int ct = 0;
-        while (ToggleableTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertEquals(state, ToggleableTestTileService.isListening());
-    }
-
-    /**
-     * Waits for the TileService to be in the expected connected state. If it times out, it fails
-     * the test
-     * @param state desired connected state
-     * @throws InterruptedException
-     */
-    @Override
-    protected void waitForConnected(boolean state) throws InterruptedException {
-        int ct = 0;
-        while (ToggleableTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertEquals(state, ToggleableTestTileService.isConnected());
-    }
-}
diff --git a/tests/app/src/android/app/cts/BroadcastOptionsTest.java b/tests/app/src/android/app/cts/BroadcastOptionsTest.java
index c3b4e89..a63f91b 100644
--- a/tests/app/src/android/app/cts/BroadcastOptionsTest.java
+++ b/tests/app/src/android/app/cts/BroadcastOptionsTest.java
@@ -16,16 +16,30 @@
 
 package android.app.cts;
 
+import static android.app.cts.ActivityManagerFgsBgStartTest.PACKAGE_NAME_APP1;
+import static android.app.cts.ActivityManagerFgsBgStartTest.PACKAGE_NAME_APP2;
+import static android.app.cts.ActivityManagerFgsBgStartTest.WAITFOR_MSEC;
+import static android.app.stubs.LocalForegroundService.ACTION_START_FGS_RESULT;
+
 import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertThrows;
 
 import android.app.BroadcastOptions;
+import android.app.Instrumentation;
+import android.app.cts.android.app.cts.tools.WaitForBroadcast;
+import android.app.stubs.CommandReceiver;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.PowerExemptionManager;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -74,9 +88,12 @@
         BroadcastOptions bo;
 
         bo = BroadcastOptions.makeBasic();
+        Bundle bundle = bo.toBundle();
 
-        // If no options are set, toBundle() should return null
-        assertNull(bo.toBundle());
+        // Only background activity launch key is set.
+        assertEquals(1, bundle.size());
+        // TODO: Use BroadcastOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED instead.
+        assertTrue(bundle.containsKey("android.pendingIntent.backgroundActivityAllowed"));
 
         // Check the default values about temp-allowlist.
         assertBroadcastOption_noTemporaryAppAllowList(bo);
@@ -162,4 +179,88 @@
         final BroadcastOptions cloned = cloneViaBundle(bo);
         assertEquals(Build.VERSION_CODES.P, bo.getMaxManifestReceiverApiLevel());
     }
+
+    @Test
+    public void testGetSetPendingIntentBackgroundActivityLaunchAllowed() {
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityLaunchAllowed(true);
+        assertTrue(options.isPendingIntentBackgroundActivityLaunchAllowed());
+        options.setPendingIntentBackgroundActivityLaunchAllowed(false);
+        assertFalse(options.isPendingIntentBackgroundActivityLaunchAllowed());
+    }
+
+    private void assertBroadcastSuccess(BroadcastOptions options) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final WaitForBroadcast waiter = new WaitForBroadcast(instrumentation.getTargetContext());
+        waiter.prepare(ACTION_START_FGS_RESULT);
+        CommandReceiver.sendCommandWithBroadcastOptions(instrumentation.getContext(),
+                CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
+                options.toBundle());
+        waiter.doWait(WAITFOR_MSEC);
+    }
+
+    private void assertBroadcastFailure(BroadcastOptions options) {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        final WaitForBroadcast waiter = new WaitForBroadcast(instrumentation.getTargetContext());
+        waiter.prepare(ACTION_START_FGS_RESULT);
+        CommandReceiver.sendCommandWithBroadcastOptions(instrumentation.getContext(),
+                CommandReceiver.COMMAND_START_FOREGROUND_SERVICE,
+                PACKAGE_NAME_APP1, PACKAGE_NAME_APP2, 0, null,
+                options.toBundle());
+        assertThrows(Exception.class, () -> waiter.doWait(WAITFOR_MSEC));
+    }
+
+    @Test
+    public void testRequireCompatChange_simple() {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            final int uid = android.os.Process.myUid();
+            final BroadcastOptions options = BroadcastOptions.makeBasic();
+
+            // Default passes
+            assertTrue(options.testRequireCompatChange(uid));
+            assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+
+            // Verify both enabled and disabled
+            options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, true);
+            assertTrue(options.testRequireCompatChange(uid));
+            assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+            options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, false);
+            assertFalse(options.testRequireCompatChange(uid));
+            assertFalse(cloneViaBundle(options).testRequireCompatChange(uid));
+
+            // And back to default passes
+            options.clearRequireCompatChange();
+            assertTrue(options.testRequireCompatChange(uid));
+            assertTrue(cloneViaBundle(options).testRequireCompatChange(uid));
+        });
+    }
+
+    @Test
+    public void testRequireCompatChange_enabled_success() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, true);
+        assertBroadcastSuccess(options);
+    }
+
+    @Test
+    public void testRequireCompatChange_enabled_failure() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_DISABLED, true);
+        assertBroadcastFailure(options);
+    }
+
+    @Test
+    public void testRequireCompatChange_disabled_success() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_DISABLED, false);
+        assertBroadcastSuccess(options);
+    }
+
+    @Test
+    public void testRequireCompatChange_disabled_failure() {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireCompatChange(BroadcastOptions.CHANGE_ALWAYS_ENABLED, false);
+        assertBroadcastFailure(options);
+    }
 }
diff --git a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
index f54c5af..da2c43a 100644
--- a/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
+++ b/tests/app/src/android/app/cts/CloseSystemDialogsTest.java
@@ -28,6 +28,7 @@
 import static org.junit.Assume.assumeTrue;
 import static org.testng.Assert.assertThrows;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.UiAutomation;
@@ -47,7 +48,10 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
 import android.os.ResultReceiver;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.provider.Settings;
 import android.server.wm.WindowManagerStateHelper;
 import android.view.Display;
@@ -118,6 +122,7 @@
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getTargetContext();
+        PermissionUtils.grantPermission(APP_SELF, Manifest.permission.POST_NOTIFICATIONS);
         mResolver = mContext.getContentResolver();
         mMainHandler = new Handler(Looper.getMainLooper());
         toggleListenerAccess(mContext, true);
@@ -163,6 +168,15 @@
         compat(APP_COMPAT_RESET, ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, APP_HELPER);
         compat(APP_COMPAT_RESET, "NOTIFICATION_TRAMPOLINE_BLOCK", APP_HELPER);
         mNotificationListener.resetData();
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
     }
 
     /** Intent.ACTION_CLOSE_SYSTEM_DIALOGS */
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index bbc57be..c22798c 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -63,6 +63,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.After;
 import org.junit.Before;
@@ -112,7 +113,7 @@
         mScenario.onActivity(activity -> {
             mActivity = activity;
         });
-        PollingCheck.waitFor(mActivity.getDialog().getWindow().getDecorView()::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity.getDialog().getWindow());
     }
 
     @UiThreadTest
diff --git a/tests/app/src/android/app/cts/InstrumentationTest.java b/tests/app/src/android/app/cts/InstrumentationTest.java
index 0d7b395..70499b8 100644
--- a/tests/app/src/android/app/cts/InstrumentationTest.java
+++ b/tests/app/src/android/app/cts/InstrumentationTest.java
@@ -48,8 +48,8 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.Window;
 
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.WindowUtil;
 
 import java.util.List;
 
@@ -75,7 +75,7 @@
         mIntent = new Intent(mContext, InstrumentationTestActivity.class);
         mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mActivity = (InstrumentationTestActivity) mInstrumentation.startActivitySync(mIntent);
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     protected void tearDown() throws Exception {
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index e3c5c9a..0fb6f50 100755
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -16,6 +16,10 @@
 
 package android.app.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+import static android.app.Notification.CATEGORY_CALL;
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_ALL;
 import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE;
@@ -39,6 +43,7 @@
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_MESSAGES;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REMINDERS;
+import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_SYSTEM;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
@@ -83,6 +88,7 @@
 import android.app.stubs.AutomaticZenRuleActivity;
 import android.app.stubs.BubbledActivity;
 import android.app.stubs.BubblesTestService;
+import android.app.stubs.GetResultActivity;
 import android.app.stubs.R;
 import android.app.stubs.SendBubbleActivity;
 import android.app.stubs.TestNotificationListener;
@@ -119,6 +125,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.platform.test.annotations.AsbSecurityTest;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
@@ -183,9 +191,12 @@
     final boolean DEBUG = false;
     static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
 
-    private static final String DELEGATOR = "com.android.test.notificationdelegator";
-    private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
-    private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
+    private static final String TEST_APP = "com.android.test.notificationapp";
+    private static final String DELEGATE_POST_CLASS = TEST_APP + ".NotificationDelegateAndPost";
+    private static final String REVOKE_CLASS = TEST_APP + ".NotificationRevoker";
+    private static final String MATCHES_CALL_FILTER_CLASS =
+            TEST_APP + ".MatchesCallFilterTestActivity";
+    private static final String MINIMAL_LISTENER_CLASS = TEST_APP + ".TestNotificationListener";
     private static final String SHARE_SHORTCUT_ID = "shareShortcut";
     private static final String SHARE_SHORTCUT_CATEGORY =
             "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
@@ -204,12 +215,28 @@
             new ComponentName(TRAMPOLINE_APP_API_30,
                     "com.android.test.notificationtrampoline.NotificationTrampolineTestService");
 
+    private static final String STUB_PACKAGE_NAME = "android.app.stubs";
+
     private static final long TIMEOUT_LONG_MS = 10000;
     private static final long TIMEOUT_MS = 4000;
     private static final int MESSAGE_BROADCAST_NOTIFICATION = 1;
     private static final int MESSAGE_SERVICE_NOTIFICATION = 2;
     private static final int MESSAGE_CLICK_NOTIFICATION = 3;
 
+    // Constants for creating contacts
+    private static final String ALICE = "Alice";
+    private static final String ALICE_PHONE = "+16175551212";
+    private static final String ALICE_EMAIL = "alice@_foo._bar";
+    private static final String BOB = "Bob";
+    private static final String BOB_PHONE = "+16175553434";
+    private static final String BOB_EMAIL = "bob@_foo._bar";
+
+    // Constants for GetResultActivity and return codes from MatchesCallFilterTestActivity
+    // the permitted/not permitted values need to stay the same as in the test activity.
+    private static final int REQUEST_CODE = 42;
+    private static final int MATCHES_CALL_FILTER_NOT_PERMITTED = 0;
+    private static final int MATCHES_CALL_FILTER_PERMITTED = 1;
+
     private PackageManager mPackageManager;
     private AudioManager mAudioManager;
     private RoleManager mRoleManager;
@@ -231,6 +258,11 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(TEST_APP, POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
+        PermissionUtils.grantPermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
         // This will leave a set of channels on the device with each test run.
         mId = UUID.randomUUID().toString();
         mNotificationManager = (NotificationManager) mContext.getSystemService(
@@ -322,6 +354,20 @@
         if (mPreviousDefaultBrowser != null) {
             restoreDefaultBrowser();
         }
+
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                android.os.Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
+        PermissionUtils.revokePermission(STUB_PACKAGE_NAME, POST_NOTIFICATIONS);
+        PermissionUtils.revokePermission(TEST_APP, POST_NOTIFICATIONS);
+        PermissionUtils.revokePermission(TRAMPOLINE_APP, POST_NOTIFICATIONS);
+        PermissionUtils.revokePermission(NOTIFICATIONPROVIDER, POST_NOTIFICATIONS);
     }
 
     private void assertNotificationCancelled(int id, boolean all) {
@@ -504,6 +550,11 @@
     }
 
     private void sendNotification(final int id, String groupKey, final int icon) throws Exception {
+        sendNotification(id, groupKey, icon, false, null);
+    }
+
+    private void sendNotification(final int id, String groupKey, final int icon,
+            boolean isCall, Uri phoneNumber) throws Exception {
         final Intent intent = new Intent(Intent.ACTION_MAIN, Threads.CONTENT_URI);
 
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
@@ -512,15 +563,26 @@
 
         final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
                 PendingIntent.FLAG_MUTABLE);
-        final Notification notification =
-                new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
                         .setSmallIcon(icon)
                         .setWhen(System.currentTimeMillis())
                         .setContentTitle("notify#" + id)
                         .setContentText("This is #" + id + "notification  ")
                         .setContentIntent(pendingIntent)
-                        .setGroup(groupKey)
-                        .build();
+                        .setGroup(groupKey);
+
+        if (isCall) {
+            nb.setCategory(CATEGORY_CALL);
+            if (phoneNumber != null) {
+                Bundle extras = new Bundle();
+                ArrayList<Person> pList = new ArrayList<>();
+                pList.add(new Person.Builder().setUri(phoneNumber.toString()).build());
+                extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList);
+                nb.setExtras(extras);
+            }
+        }
+
+        final Notification notification = nb.build();
         mNotificationManager.notify(id, notification);
 
         if (!checkNotificationExistence(id, /*shouldExist=*/ true)) {
@@ -738,6 +800,24 @@
         runCommand(command, InstrumentationRegistry.getInstrumentation());
     }
 
+    private boolean hasReadContactsPermission(String pkgName) {
+        return mPackageManager.checkPermission(
+                Manifest.permission.READ_CONTACTS, pkgName)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private void toggleReadContactsPermission(String pkgName, boolean on) {
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            if (on) {
+                mInstrumentation.getUiAutomation().grantRuntimePermission(pkgName,
+                        "android.permission.READ_CONTACTS");
+            } else {
+                mInstrumentation.getUiAutomation().revokeRuntimePermission(pkgName,
+                        "android.permission.READ_CONTACTS");
+            }
+        });
+    }
+
     private void setBubblesGlobal(boolean enabled)
             throws InterruptedException {
         SystemUtil.runWithShellPermissionIdentity(() ->
@@ -940,6 +1020,17 @@
         mContext.unregisterReceiver(mBubbleBroadcastReceiver);
     }
 
+    // Creates a GetResultActivity into which one can call startActivityForResult with
+    // in order to test the outcome of an activity that returns a result code.
+    private GetResultActivity setUpGetResultActivity() {
+        final Intent intent = new Intent(mContext, GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        GetResultActivity activity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
+        mInstrumentation.waitForIdleSync();
+        activity.clearResult();
+        return activity;
+    }
+
     private void sendTrampolineMessage(ComponentName component, int message,
             int notificationId, Handler callback) throws Exception {
         if (mTrampolineConnection == null) {
@@ -2537,7 +2628,7 @@
     public void testNotificationDelegate_grantAndPost() throws Exception {
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setPackage(TEST_APP);
         activityIntent.setAction(Intent.ACTION_MAIN);
         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2550,11 +2641,11 @@
         Notification n = new Notification.Builder(mContext, "channel")
                 .setSmallIcon(android.R.id.icon)
                 .build();
-        mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
+        mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
 
         assertNotNull(findPostedNotification(0, false));
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(1000);
@@ -2563,7 +2654,7 @@
     public void testNotificationDelegate_grantAndPostAndCancel() throws Exception {
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setPackage(TEST_APP);
         activityIntent.setAction(Intent.ACTION_MAIN);
         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2576,12 +2667,12 @@
         Notification n = new Notification.Builder(mContext, "channel")
                 .setSmallIcon(android.R.id.icon)
                 .build();
-        mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
+        mNotificationManager.notifyAsPackage(TEST_APP, "toBeCanceled", 10000, n);
         assertNotNull(findPostedNotification(10000, false));
-        mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
+        mNotificationManager.cancelAsPackage(TEST_APP, "toBeCanceled", 10000);
         assertNotificationCancelled(10000, false);
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(1000);
@@ -2597,7 +2688,7 @@
 
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setClassName(DELEGATOR, DELEGATE_POST_CLASS);
+        activityIntent.setClassName(TEST_APP, DELEGATE_POST_CLASS);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
 
         mContext.startActivity(activityIntent);
@@ -2607,7 +2698,7 @@
         assertNotNull(findPostedNotification(9, true));
 
         try {
-            mNotificationManager.cancelAsPackage(DELEGATOR, null, 9);
+            mNotificationManager.cancelAsPackage(TEST_APP, null, 9);
             fail("Delegate should not be able to cancel notification they did not post");
         } catch (SecurityException e) {
             // yay
@@ -2617,7 +2708,7 @@
         assertNotNull(findPostedNotification(9, true));
 
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(1000);
@@ -2626,7 +2717,7 @@
     public void testNotificationDelegate_grantAndReadChannels() throws Exception {
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setPackage(TEST_APP);
         activityIntent.setAction(Intent.ACTION_MAIN);
         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2636,14 +2727,14 @@
         Thread.sleep(500);
 
         List<NotificationChannel> channels =
-                mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
+                mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
                         .getSystemService(NotificationManager.class)
                         .getNotificationChannels();
 
         assertNotNull(channels);
 
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(500);
@@ -2652,7 +2743,7 @@
     public void testNotificationDelegate_grantAndReadChannel() throws Exception {
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setPackage(TEST_APP);
         activityIntent.setAction(Intent.ACTION_MAIN);
         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2662,14 +2753,14 @@
         Thread.sleep(2000);
 
         NotificationChannel channel =
-                mContext.createPackageContextAsUser(DELEGATOR, /* flags= */ 0, mContext.getUser())
+                mContext.createPackageContextAsUser(TEST_APP, /* flags= */ 0, mContext.getUser())
                         .getSystemService(NotificationManager.class)
                         .getNotificationChannel("channel");
 
         assertNotNull(channel);
 
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(500);
@@ -2678,7 +2769,7 @@
     public void testNotificationDelegate_grantAndRevoke() throws Exception {
         // grant this test permission to post
         final Intent activityIntent = new Intent();
-        activityIntent.setPackage(DELEGATOR);
+        activityIntent.setPackage(TEST_APP);
         activityIntent.setAction(Intent.ACTION_MAIN);
         activityIntent.addCategory(Intent.CATEGORY_LAUNCHER);
         activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2686,10 +2777,10 @@
         mContext.startActivity(activityIntent);
         Thread.sleep(500);
 
-        assertTrue(mNotificationManager.canNotifyAsPackage(DELEGATOR));
+        assertTrue(mNotificationManager.canNotifyAsPackage(TEST_APP));
 
         final Intent revokeIntent = new Intent();
-        revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
+        revokeIntent.setClassName(TEST_APP, REVOKE_CLASS);
         revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(revokeIntent);
         Thread.sleep(500);
@@ -2699,7 +2790,7 @@
             Notification n = new Notification.Builder(mContext, "channel")
                     .setSmallIcon(android.R.id.icon)
                     .build();
-            mNotificationManager.notifyAsPackage(DELEGATOR, "tag", 0, n);
+            mNotificationManager.notifyAsPackage(TEST_APP, "tag", 0, n);
             fail("Should not be able to post as a delegate when permission revoked");
         } catch (SecurityException e) {
             // yay
@@ -2786,44 +2877,274 @@
         mNotificationManager.shouldHideSilentStatusBarIcons();
     }
 
-    public void testMatchesCallFilter() throws Exception {
+    public void testMatchesCallFilter_noPermissions() {
+        // make sure we definitely don't have contacts access
+        boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+        try {
+            toggleReadContactsPermission(TEST_APP, false);
+
+            // start an activity that has no permissions, which will run matchesCallFilter on
+            // a meaningless uri. The result code indicates whether or not the method call was
+            // permitted.
+            final Intent mcfIntent = new Intent();
+            mcfIntent.setPackage(TEST_APP);
+            mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+            GetResultActivity grActivity = setUpGetResultActivity();
+            grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+            UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+            // with no permissions, this call should not have been permitted
+            GetResultActivity.Result result = grActivity.getResult();
+            assertEquals(REQUEST_CODE, result.requestCode);
+            assertEquals(MATCHES_CALL_FILTER_NOT_PERMITTED, result.resultCode);
+            grActivity.finishActivity(REQUEST_CODE);
+        } finally {
+            toggleReadContactsPermission(TEST_APP, hadReadPerm);
+        }
+    }
+
+    public void testMatchesCallFilter_listenerPermissionOnly() throws Exception {
+        boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+        // minimal listener service so that it can be given listener permissions
+        final ComponentName listenerComponent =
+                new ComponentName(TEST_APP, MINIMAL_LISTENER_CLASS);
+        try {
+            // make surethat we don't for some reason have contacts access
+            toggleReadContactsPermission(TEST_APP, false);
+
+            // grant the notification app package notification listener access;
+            // give it time to succeed
+            toggleExternalListenerAccess(listenerComponent, true);
+            Thread.sleep(500);
+
+            // set up & run intent
+            final Intent mcfIntent = new Intent();
+            mcfIntent.setPackage(TEST_APP);
+            mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+            GetResultActivity grActivity = setUpGetResultActivity();
+            grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+            UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+            // with just listener permissions, this call should have been permitted
+            GetResultActivity.Result result = grActivity.getResult();
+            assertEquals(REQUEST_CODE, result.requestCode);
+            assertEquals(MATCHES_CALL_FILTER_PERMITTED, result.resultCode);
+            grActivity.finishActivity(REQUEST_CODE);
+        } finally {
+            // clean up listener access, reset read contacts access
+            toggleExternalListenerAccess(listenerComponent, false);
+            toggleReadContactsPermission(TEST_APP, hadReadPerm);
+        }
+    }
+
+    public void testMatchesCallFilter_contactsPermissionOnly() throws Exception {
+        // grant the notification app package contacts read access
+        boolean hadReadPerm = hasReadContactsPermission(TEST_APP);
+        try {
+            toggleReadContactsPermission(TEST_APP, true);
+
+            // set up & run intent
+            final Intent mcfIntent = new Intent();
+            mcfIntent.setPackage(TEST_APP);
+            mcfIntent.setClassName(TEST_APP, MATCHES_CALL_FILTER_CLASS);
+            GetResultActivity grActivity = setUpGetResultActivity();
+            grActivity.startActivityForResult(mcfIntent, REQUEST_CODE);
+            UiDevice.getInstance(mInstrumentation).waitForIdle();
+
+            // with just contacts read permissions, this call should have been permitted
+            GetResultActivity.Result result = grActivity.getResult();
+            assertEquals(REQUEST_CODE, result.requestCode);
+            assertEquals(MATCHES_CALL_FILTER_PERMITTED, result.resultCode);
+            grActivity.finishActivity(REQUEST_CODE);
+        } finally {
+            // clean up contacts access
+            toggleReadContactsPermission(TEST_APP, hadReadPerm);
+        }
+    }
+
+    public void testMatchesCallFilter_zenOff() throws Exception {
+        // zen mode is not on so nothing is filtered; matchesCallFilter should always pass
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+        try {
+            // allowed from anyone: nothing is filtered, and make sure change went through
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
+            assertExpectedDndState(INTERRUPTION_FILTER_ALL);
+
+            // create a phone URI from which to receive a call
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode("+16175551212"));
+            assertTrue(mNotificationManager.matchesCallFilter(phoneUri));
+        } finally {
+            mNotificationManager.setInterruptionFilter(origFilter);
+        }
+    }
+
+    public void testMatchesCallFilter_noCallInterruptions() throws Exception {
+        // when no call interruptions are allowed at all, or only alarms, matchesCallFilter
+        // should always fail
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+        Policy origPolicy = mNotificationManager.getNotificationPolicy();
+        try {
+            // create a phone URI from which to receive a call
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode("+16175551212"));
+
+            // no interruptions allowed at all
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_NONE);
+            assertExpectedDndState(INTERRUPTION_FILTER_NONE);
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+            // only alarms
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALARMS);
+            assertExpectedDndState(INTERRUPTION_FILTER_ALARMS);
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_MESSAGES, 0, 0));
+            // turn on manual DND
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+        } finally {
+            mNotificationManager.setInterruptionFilter(origFilter);
+            mNotificationManager.setNotificationPolicy(origPolicy);
+        }
+    }
+
+    public void testMatchesCallFilter_someCallers() throws Exception {
+        // zen mode is active; check various configurations where some calls, but not all calls,
+        // are allowed
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+        Policy origPolicy = mNotificationManager.getNotificationPolicy();
+        Uri aliceUri = null;
+        Uri bobUri = null;
+        try {
+            // set up phone numbers: one starred, one regular, one unknown number
+            // starred contact from whom to receive a call
+            insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
+            aliceUri = lookupContact(ALICE_PHONE);
+
+            // non-starred contact from whom to also receive a call
+            insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
+            bobUri = lookupContact(BOB_PHONE);
+
+            // non-contact phone URI
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode("+16175555656"));
+
+            // set up: any contacts are allowed to call.
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_CALLS,
+                    NotificationManager.Policy.PRIORITY_SENDERS_CONTACTS, 0));
+
+            // turn on manual DND
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            // in this case Alice and Bob should get through but not the unknown number.
+            assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
+            assertTrue(mNotificationManager.matchesCallFilter(bobUri));
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+            // set up: only starred contacts are allowed to call.
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_CALLS,
+                    NotificationManager.Policy.PRIORITY_SENDERS_STARRED, 0));
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            // now only Alice should be allowed to get through
+            assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
+            assertFalse(mNotificationManager.matchesCallFilter(bobUri));
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+        } finally {
+            mNotificationManager.setInterruptionFilter(origFilter);
+            mNotificationManager.setNotificationPolicy(origPolicy);
+            if (aliceUri != null) {
+                // delete the contact
+                deleteSingleContact(aliceUri);
+            }
+            if (bobUri != null) {
+                deleteSingleContact(bobUri);
+            }
+        }
+    }
+
+    public void testMatchesCallFilter_repeatCallers() throws Exception {
+        // if repeat callers are allowed, an unknown number calling twice should go through
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        int origFilter = mNotificationManager.getCurrentInterruptionFilter();
+        Policy origPolicy = mNotificationManager.getNotificationPolicy();
+        long startTime = System.currentTimeMillis();
+        try {
+            // create a phone URI from which to receive a call
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode("+16175551212"));
+
+            mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(
+                    PRIORITY_CATEGORY_REPEAT_CALLERS, 0, 0));
+            // turn on manual DND
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
+
+            // not a repeat caller yet, so it shouldn't be allowed
+            assertFalse(mNotificationManager.matchesCallFilter(phoneUri));
+
+            // register a call from this number, then cancel the notification, which is when
+            // a call is actually recorded.
+            sendNotification(1, null, R.drawable.blue, true, phoneUri);
+            cancelAndPoll(1);
+
+            // now this number should count as a repeat caller
+            assertTrue(mNotificationManager.matchesCallFilter(phoneUri));
+        } finally {
+            mNotificationManager.setInterruptionFilter(origFilter);
+            mNotificationManager.setNotificationPolicy(origPolicy);
+
+            // make sure we clean up the recent call, otherwise future runs of this will fail
+            // and we'll have a fake call still kicking around somewhere.
+            SystemUtil.runWithShellPermissionIdentity(() ->
+                    mNotificationManager.cleanUpCallersAfter(startTime));
+        }
+    }
+
+    public void testMatchesCallFilter_allCallers() throws Exception {
         // allow all callers
         toggleNotificationPolicyAccess(mContext.getPackageName(),
                 InstrumentationRegistry.getInstrumentation(), true);
+        int origFilter = mNotificationManager.getCurrentInterruptionFilter();
         Policy origPolicy = mNotificationManager.getNotificationPolicy();
         Uri aliceUri = null;
         try {
             NotificationManager.Policy currPolicy = mNotificationManager.getNotificationPolicy();
             NotificationManager.Policy newPolicy = new NotificationManager.Policy(
                     NotificationManager.Policy.PRIORITY_CATEGORY_CALLS
-                            | NotificationManager.Policy.PRIORITY_CATEGORY_REPEAT_CALLERS,
+                            | PRIORITY_CATEGORY_REPEAT_CALLERS,
                     NotificationManager.Policy.PRIORITY_SENDERS_ANY,
                     currPolicy.priorityMessageSenders,
                     currPolicy.suppressedVisualEffects);
             mNotificationManager.setNotificationPolicy(newPolicy);
-
-            // add a contact
-            String ALICE = "Alice";
-            String ALICE_PHONE = "+16175551212";
-            String ALICE_EMAIL = "alice@_foo._bar";
+            mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY);
+            assertExpectedDndState(INTERRUPTION_FILTER_PRIORITY);
 
             insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, false);
 
-            final Bundle peopleExtras = new Bundle();
-            ArrayList<Person> personList = new ArrayList<>();
             aliceUri = lookupContact(ALICE_PHONE);
-            personList.add(new Person.Builder().setUri(aliceUri.toString()).build());
-            peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
-            SystemUtil.runWithShellPermissionIdentity(() ->
-                    assertTrue(mNotificationManager.matchesCallFilter(peopleExtras)));
+            assertTrue(mNotificationManager.matchesCallFilter(aliceUri));
         } finally {
+            mNotificationManager.setInterruptionFilter(origFilter);
             mNotificationManager.setNotificationPolicy(origPolicy);
             if (aliceUri != null) {
                 // delete the contact
                 deleteSingleContact(aliceUri);
             }
         }
-
     }
 
     /* Confirm that the optional methods of TestNotificationListener still exist and
@@ -3239,7 +3560,8 @@
         }
 
         mListener.cancelNotifications(new String[]{ sbn.getKey() });
-        if (!checkNotificationExistence(notificationId, /*shouldExist=*/ false)) {
+        if (getCancellationReason(sbn.getKey())
+                != NotificationListenerService.REASON_LISTENER_CANCEL) {
             fail("Failed to cancel notification id=" + notificationId);
         }
     }
diff --git a/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt b/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt
new file mode 100644
index 0000000..e7924bc
--- /dev/null
+++ b/tests/app/src/android/app/cts/RequestTileServiceAddTest.kt
@@ -0,0 +1,235 @@
+/*
+ * 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.app.cts
+
+import android.Manifest.permission.STATUS_BAR
+import android.app.Activity
+import android.app.ActivityManager
+import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
+import android.app.Instrumentation
+import android.app.StatusBarManager
+import android.app.stubs.MockActivity
+import android.app.stubs.NotExportedTestTileService
+import android.app.stubs.TestTileService
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.graphics.drawable.Icon
+import android.service.quicksettings.TileService
+import androidx.test.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.SystemUtil
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+
+/**
+ * Test that the request fails in all the expected ways.
+ *
+ * These tests are for [StatusBarManager.requestAddTileService].
+ */
+@RunWith(AndroidJUnit4::class)
+class RequestTileServiceAddTest {
+
+    companion object {
+        private const val LABEL = "label"
+        private const val TIME_OUT_SECONDS = 5L
+    }
+
+    private lateinit var statusBarService: StatusBarManager
+    private lateinit var context: Context
+    private lateinit var icon: Icon
+    private lateinit var consumer: StoreIntConsumer
+    private lateinit var instrumentation: Instrumentation
+    private val executor = MoreExecutors.directExecutor()
+    private lateinit var latch: CountDownLatch
+
+    @Before
+    fun setUp() {
+        Assume.assumeTrue(TileService.isQuickSettingsSupported())
+
+        instrumentation = InstrumentationRegistry.getInstrumentation()
+        context = instrumentation.getTargetContext()
+        statusBarService = context.getSystemService(StatusBarManager::class.java)!!
+
+        icon = Icon.createWithResource(context, R.drawable.ic_android)
+        latch = CountDownLatch(1)
+        consumer = StoreIntConsumer(latch)
+    }
+
+    @Test
+    fun testRequestBadPackageFails() {
+        val componentName = ComponentName("test_pkg", "test_cls")
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+        assertThat(consumer.result)
+                .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_MISMATCHED_PACKAGE)
+    }
+
+    @Test
+    fun testRequestBadComponentName() {
+        val componentName = ComponentName(context, "test_cls")
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+        assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+    }
+
+    @Test
+    fun testDisabledComponent() {
+        val componentName = TestTileService.getComponentName()
+        context.packageManager.setComponentEnabledSetting(
+                componentName,
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.SYNCHRONOUS or PackageManager.DONT_KILL_APP
+        )
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+        assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+
+        // Cleanup
+        context.packageManager.setComponentEnabledSetting(
+                componentName,
+                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
+                PackageManager.SYNCHRONOUS or PackageManager.DONT_KILL_APP
+        )
+    }
+
+    @Test
+    fun testNotExportedComponent() {
+        val componentName = NotExportedTestTileService.getComponentName()
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+        assertThat(consumer.result).isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_BAD_COMPONENT)
+    }
+
+    @Test
+    fun testAppNotInForeground() {
+        // This test is never run in foreground, so it's a good candidate for testing this
+        val componentName = TestTileService.getComponentName()
+        val activityManager = context.getSystemService(ActivityManager::class.java)!!
+        assertThat(activityManager.getPackageImportance(context.packageName))
+                .isNotEqualTo(IMPORTANCE_FOREGROUND)
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+
+        assertThat(consumer.result)
+                .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_APP_NOT_IN_FOREGROUND)
+    }
+
+    @Test
+    fun testTwoSimultaneousRequests() {
+        // We need an activity in the foreground for the first request to not be denied
+        val activity = setUpForActivity()
+        val componentName = TestTileService.getComponentName()
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                {}
+        )
+
+        Thread.sleep(TimeUnit.SECONDS.toMillis(TIME_OUT_SECONDS))
+
+        statusBarService.requestAddTileService(
+                componentName,
+                LABEL,
+                icon,
+                executor,
+                consumer
+        )
+
+        latch.await(TIME_OUT_SECONDS, TimeUnit.SECONDS)
+        assertThat(consumer.result)
+                .isEqualTo(StatusBarManager.TILE_ADD_REQUEST_ERROR_REQUEST_IN_PROGRESS)
+
+        SystemUtil.callWithShellPermissionIdentity(
+                { statusBarService.cancelRequestAddTile(componentName.packageName) },
+                STATUS_BAR
+        )
+
+        activity.finish()
+    }
+
+    private fun setUpForActivity(): Activity {
+        val intent = Intent(context, MockActivity::class.java)
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        val activity = instrumentation.startActivitySync(intent)
+        instrumentation.waitForIdleSync()
+        return activity
+    }
+
+    private class StoreIntConsumer(private val latch: CountDownLatch) : Consumer<Int> {
+        var result: Int? = null
+
+        override fun accept(t: Int) {
+            result = t
+            latch.countDown()
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/ServiceTest.java b/tests/app/src/android/app/cts/ServiceTest.java
index 2d18509..ee3c06f 100644
--- a/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/app/src/android/app/cts/ServiceTest.java
@@ -16,6 +16,9 @@
 
 package android.app.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
 import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND;
 import static android.app.stubs.LocalForegroundService.COMMAND_START_FOREGROUND_DEFER_NOTIFICATION;
 import static android.app.stubs.LocalForegroundService.COMMAND_STOP_FOREGROUND_DETACH_NOTIFICATION;
@@ -53,6 +56,8 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.service.notification.StatusBarNotification;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.util.Log;
@@ -698,6 +703,7 @@
     protected void setUp() throws Exception {
         super.setUp();
         mContext = getContext();
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         mLocalService = new Intent(mContext, LocalService.class);
         mExternalService = new Intent();
         mExternalService.setComponent(ComponentName.unflattenFromString(EXTERNAL_SERVICE_COMPONENT));
@@ -745,6 +751,15 @@
         }
         mBackgroundThread = null;
         mBackgroundThreadExecutor = null;
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
     }
 
     private class MockBinder extends Binder {
diff --git a/tests/app/src/android/app/cts/StatusBarManagerTest.java b/tests/app/src/android/app/cts/StatusBarManagerTest.java
index 9995099..7c4f6dd 100644
--- a/tests/app/src/android/app/cts/StatusBarManagerTest.java
+++ b/tests/app/src/android/app/cts/StatusBarManagerTest.java
@@ -18,6 +18,8 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
@@ -40,7 +42,7 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class StatusBarManagerTest {
-    private static final String PERMISSION_STATUS_BAR = "android.permission.STATUS_BAR";
+    private static final String PERMISSION_STATUS_BAR = android.Manifest.permission.STATUS_BAR;
 
     private StatusBarManager mStatusBarManager;
     private Context mContext;
@@ -94,6 +96,7 @@
         assertTrue(info.isStatusBarExpansionDisabled());
         assertTrue(info.isRecentsDisabled());
         assertTrue(info.isSearchDisabled());
+        assertFalse(info.isRotationSuggestionDisabled());
     }
 
     /**
@@ -179,4 +182,41 @@
 
         // Nothing thrown, passed
     }
+
+    /**
+     * Test StatusBarManager.setNavBarModeOverride(NAV_BAR_MODE_OVERRIDE_KIDS)
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetNavBarModeOverride_kids_doesNotThrow() throws Exception {
+        int navBarModeOverrideKids = StatusBarManager.NAV_BAR_MODE_OVERRIDE_KIDS;
+        mStatusBarManager.setNavBarModeOverride(navBarModeOverrideKids);
+
+        assertEquals(mStatusBarManager.getNavBarModeOverride(), navBarModeOverrideKids);
+    }
+
+    /**
+     * Test StatusBarManager.setNavBarModeOverride(NAV_BAR_MODE_OVERRIDE_NONE)
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testSetNavBarModeOverride_none_doesNotThrow() throws Exception {
+        int navBarModeOverrideNone = StatusBarManager.NAV_BAR_MODE_OVERRIDE_NONE;
+        mStatusBarManager.setNavBarModeOverride(navBarModeOverrideNone);
+
+        assertEquals(mStatusBarManager.getNavBarModeOverride(), navBarModeOverrideNone);
+    }
+
+    /**
+     * Test StatusBarManager.setNavBarModeOverride(-1) // invalid input
+     *
+     * @throws Exception
+     */
+    @Test(expected = UnsupportedOperationException.class)
+    public void testSetNavBarModeOverride_invalid_throws() throws Exception {
+        int invalidInput = -1;
+        mStatusBarManager.setNavBarModeOverride(invalidInput);
+    }
 }
diff --git a/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 19dc7a3..b850606 100644
--- a/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -508,7 +508,7 @@
     @Test
     public void testTelephonyFeatures() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) ||
-            !mPackageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE)) {
+                !mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELECOM)) {
                 return;
         }
 
diff --git a/tests/app/src/android/app/cts/TileServiceTest.java b/tests/app/src/android/app/cts/TileServiceTest.java
deleted file mode 100644
index 2509eb7..0000000
--- a/tests/app/src/android/app/cts/TileServiceTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.cts;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import android.app.AlertDialog;
-import android.app.Dialog;
-import android.app.stubs.TestTileService;
-import android.os.Looper;
-import android.service.quicksettings.Tile;
-import android.service.quicksettings.TileService;
-
-import org.junit.Test;
-
-public class TileServiceTest extends BaseTileServiceTest {
-    private final static String TAG = "TileServiceTest";
-
-    @Test
-    public void testCreateTileService() {
-        final TileService tileService = new TileService();
-    }
-
-    @Test
-    public void testListening() throws Exception {
-        initializeAndListen();
-    }
-
-    @Test
-    public void testListening_stopped() throws Exception {
-        initializeAndListen();
-        expandSettings(false);
-        waitForListening(false);
-    }
-
-    @Test
-    public void testLocked_deviceNotLocked() throws Exception {
-        initializeAndListen();
-        assertFalse(mTileService.isLocked());
-    }
-
-    @Test
-    public void testSecure_deviceNotSecure() throws Exception {
-        initializeAndListen();
-        assertFalse(mTileService.isSecure());
-    }
-
-    @Test
-    public void testTile_hasCorrectIcon() throws Exception {
-        initializeAndListen();
-        Tile tile = mTileService.getQsTile();
-        assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
-    }
-
-    @Test
-    public void testTile_hasCorrectSubtitle() throws Exception {
-        initializeAndListen();
-
-        Tile tile = mTileService.getQsTile();
-        tile.setSubtitle("test_subtitle");
-        tile.updateTile();
-        assertEquals("test_subtitle", tile.getSubtitle());
-    }
-
-    @Test
-    public void testTile_hasCorrectStateDescription() throws Exception {
-        initializeAndListen();
-
-        Tile tile = mTileService.getQsTile();
-        tile.setStateDescription("test_stateDescription");
-        tile.updateTile();
-        assertEquals("test_stateDescription", tile.getStateDescription());
-    }
-
-    @Test
-    public void testShowDialog() throws Exception {
-        Looper.prepare();
-        Dialog dialog = new AlertDialog.Builder(mContext).create();
-        initializeAndListen();
-        clickTile(TestTileService.getComponentName().flattenToString());
-        waitForClick();
-
-        mTileService.showDialog(dialog);
-
-        assertTrue(dialog.isShowing());
-        dialog.dismiss();
-    }
-
-    @Test
-    public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
-        initializeAndListen();
-        assertFalse(mTileService.isLocked());
-
-        TestRunnable testRunnable = new TestRunnable();
-
-        mTileService.unlockAndRun(testRunnable);
-        Thread.sleep(100); // wait for activity to run
-        waitForRun(testRunnable);
-    }
-
-    @Test
-    public void testTileInDumpAndHasState() throws Exception {
-        initializeAndListen();
-
-        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
-        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
-        final String line = findLine(dumpLines, tileLabel);
-        assertNotNull(line);
-        assertTrue(line.trim().startsWith("State")); // Not BooleanState
-    }
-
-    private void clickTile(String componentName) throws Exception {
-        executeShellCommand(" cmd statusbar click-tile " + componentName);
-    }
-
-    /**
-     * Waits for the TileService to receive the clicked event. If it times out it fails the test.
-     * @throws InterruptedException
-     */
-    private void waitForClick() throws InterruptedException {
-        int ct = 0;
-        while (!TestTileService.hasBeenClicked() && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertTrue(TestTileService.hasBeenClicked());
-    }
-
-    /**
-     * Waits for the runnable to be run. If it times out it fails the test.
-     * @throws InterruptedException
-     */
-    private void waitForRun(TestRunnable t) throws InterruptedException {
-        int ct = 0;
-        while (!t.hasRan && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertTrue(t.hasRan);
-    }
-
-    @Override
-    protected void waitForListening(boolean state) throws InterruptedException {
-        int ct = 0;
-        while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertEquals(state, TestTileService.isListening());
-    }
-
-    /**
-     * Waits for the TileService to be in the expected connected state. If it times out, it fails
-     * the test
-     * @param state desired connected state
-     * @throws InterruptedException
-     */
-    @Override
-    protected void waitForConnected(boolean state) throws InterruptedException {
-        int ct = 0;
-        while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
-            Thread.sleep(CHECK_DELAY);
-        }
-        assertEquals(state, TestTileService.isConnected());
-    }
-
-    @Override
-    protected String getTag() {
-        return TAG;
-    }
-
-    @Override
-    protected String getComponentName() {
-        return TestTileService.getComponentName().flattenToString();
-    }
-
-    @Override
-    protected TileService getTileServiceInstance() {
-        return TestTileService.getInstance();
-    }
-
-    class TestRunnable implements Runnable {
-        boolean hasRan = false;
-
-        @Override
-        public void run() {
-            hasRan = true;
-        }
-    }
-}
diff --git a/tests/app/src/android/app/cts/UiModeManagerTest.java b/tests/app/src/android/app/cts/UiModeManagerTest.java
index bf53bd9..ddb9c77 100644
--- a/tests/app/src/android/app/cts/UiModeManagerTest.java
+++ b/tests/app/src/android/app/cts/UiModeManagerTest.java
@@ -15,11 +15,23 @@
  */
 package android.app.cts;
 
+import static android.app.UiModeManager.MODE_NIGHT_AUTO;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_SCHEDULE;
+import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_UNKNOWN;
+import static android.app.UiModeManager.MODE_NIGHT_NO;
+import static android.app.UiModeManager.MODE_NIGHT_YES;
+
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
 import android.Manifest;
 import android.app.UiAutomation;
 import android.app.UiModeManager;
@@ -56,19 +68,37 @@
     private static final long WAIT_TIME_INCR_MS = 100;
 
     private UiModeManager mUiModeManager;
+    private boolean mHasModifiedNightModePermissionAcquired = false;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mUiModeManager = (UiModeManager) getContext().getSystemService(Context.UI_MODE_SERVICE);
         assertNotNull(mUiModeManager);
-        // reset nightMode
-        setNightMode(UiModeManager.MODE_NIGHT_YES);
-        setNightMode(UiModeManager.MODE_NIGHT_NO);
+        resetNightMode();
         // Make sure automotive projection is not set by this package at the beginning of the test.
         releaseAutomotiveProjection();
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        resetNightMode();
+    }
+
+    private void resetNightMode() {
+        try {
+            if (!mHasModifiedNightModePermissionAcquired) {
+                acquireModifyNightModePermission();
+            }
+            mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+            mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                    false /* active */);
+        } finally {
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+            mHasModifiedNightModePermissionAcquired = false;
+        }
+    }
+
     public void testUiMode() throws Exception {
         if (isAutomotive()) {
             Log.i(TAG, "testUiMode automotive");
@@ -148,6 +178,184 @@
         assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_AUTO);
     }
 
+    public void testSetNightModeCustomType_noPermission_bedtime_shouldThrow() {
+        assertThrows(SecurityException.class,
+                () -> mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME));
+    }
+
+    public void testSetNightModeCustomType_bedtime_shouldPersist() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertThat(mUiModeManager.getNightMode()).isEqualTo(MODE_NIGHT_CUSTOM);
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+    }
+
+    public void testSetNightModeCustomType_schedule_shouldPersist() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+
+        assertThat(mUiModeManager.getNightMode()).isEqualTo(MODE_NIGHT_CUSTOM);
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+    }
+
+    public void testGetNightModeCustomType_nightModeNo_shouldReturnUnknown() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_NO);
+
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+    }
+
+    public void testGetNightModeCustomType_nightModeYes_shouldReturnUnknown() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+    }
+
+    public void testGetNightModeCustomType_nightModeAuto_shouldReturnUnknown() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_UNKNOWN);
+    }
+
+    public void testGetNightModeCustomType_nightModeCustom_shouldReturnScheduleAsDefault() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_CUSTOM);
+
+        assertThat(mUiModeManager.getNightModeCustomType())
+                .isEqualTo(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+    }
+
+    public void testSetNightModeCustomType_hasPermission_bedtime_shouldNotActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_noPermission_customTypeBedtime_activateAtBedtime_shouldNotActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+        getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        mHasModifiedNightModePermissionAcquired = false;
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_customTypeBedtime_activateAtBedtime_shouldActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isTrue();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_customTypeBedtime_deactivateAtBedtime_shouldDeactivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, false /* active */)).isTrue();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeNo_activateAtBedtime_shouldNotActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_NO);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeYes_activateAtBedtime_shouldKeepNightModeActivated() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_YES);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeAuto_activateAtBedtime_shouldNotActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_customTypeSchedule_activateAtBedtime_shouldNotActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+
+        assertThat(mUiModeManager.setNightModeActivatedForCustomMode(
+                MODE_NIGHT_CUSTOM_TYPE_BEDTIME, true /* active */)).isFalse();
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_NO);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeNo_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_NO);
+        mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                true /* active */);
+
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeYes_activateAtBedtime_thenCustomTypeBedtime_shouldActiveNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_YES);
+        mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                true /* active */);
+
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_modeAuto_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(MODE_NIGHT_AUTO);
+        mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                true /* active */);
+
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
+    public void testSetNightModeActivatedForCustomMode_customTypeSchedule_activateAtBedtime_thenCustomTypeBedtime_shouldActivateNightMode() {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_SCHEDULE);
+        mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME,
+                true /* active */);
+
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertVisibleNightModeInConfiguration(Configuration.UI_MODE_NIGHT_YES);
+    }
+
     public void testNightModeAutoNotPersistedCarMode() {
         if (mUiModeManager.isNightModeLocked()) {
             Log.i(TAG, "testNightModeAutoNotPersistedCarMode skipped: night mode is locked");
@@ -163,6 +371,18 @@
         mUiModeManager.disableCarMode(0);
     }
 
+    public void testNightModeCustomTypeBedtimeNotPersistedInCarMode() throws InterruptedException {
+        acquireModifyNightModePermission();
+        mUiModeManager.setNightMode(UiModeManager.MODE_NIGHT_NO);
+        mUiModeManager.enableCarMode(0);
+
+        // We don't expect users modifing bedtime features when driving.
+        mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME);
+
+        assertStoredNightModeSetting(UiModeManager.MODE_NIGHT_NO);
+        mUiModeManager.disableCarMode(0);
+    }
+
     public void testNightModeInCarModeIsTransient() {
         if (mUiModeManager.isNightModeLocked()) {
             Log.i(TAG, "testNightModeInCarModeIsTransient skipped: night mode is locked");
@@ -685,4 +905,10 @@
                 ? SettingsUtils.getSecureSettingAsUser(UserHandle.USER_SYSTEM, key)
                 : SettingsUtils.getSecureSetting(key);
     }
+
+    private void acquireModifyNightModePermission() {
+        getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.MODIFY_DAY_NIGHT_MODE);
+        mHasModifiedNightModePermissionAcquired = true;
+    }
 }
diff --git a/tests/app/src/android/app/cts/UserHandleTest.java b/tests/app/src/android/app/cts/UserHandleTest.java
index ec7cc40..cb6f18d 100644
--- a/tests/app/src/android/app/cts/UserHandleTest.java
+++ b/tests/app/src/android/app/cts/UserHandleTest.java
@@ -16,27 +16,32 @@
 
 package android.app.cts;
 
+import static org.junit.Assert.assertSame;
+
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.test.AndroidTestCase;
 
 public class UserHandleTest extends AndroidTestCase {
     private static final int TEST_APP_ID = 1234;
+
     private static void assertSameUserHandle(int userId) {
         assertSame(UserHandle.of(userId), UserHandle.of(userId));
     }
 
     public void testOf() {
-        for (int i = -1000; i < 100; i++) {
-            assertEquals(i, UserHandle.of(i).getIdentifier());
-        }
-
-        // Ensure common objects are cached.
+        // We test them separately since technically it's possible for these constants to have
+        // different values than the AOSP contains and are out of the [-1000, 1000] range.
         assertSameUserHandle(UserHandle.USER_SYSTEM);
         assertSameUserHandle(UserHandle.USER_ALL);
         assertSameUserHandle(UserHandle.USER_NULL);
-        assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID);
-        assertSameUserHandle(UserHandle.MIN_SECONDARY_USER_ID + 1);
+
+        for (int userId = -1000; userId <= 1000; userId++) {
+            assertEquals(userId, UserHandle.of(userId).getIdentifier());
+
+            // Because of the cache, this should always be true.
+            assertSameUserHandle(userId);
+        }
     }
 
     private static void assertParcel(int userId) {
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 7fbd297..f2f4985 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
+import android.Manifest;
 import android.app.WallpaperColors;
 import android.app.WallpaperManager;
 import android.app.stubs.R;
@@ -68,6 +69,9 @@
 
     private static final boolean DEBUG = false;
     private static final String TAG = "WallpaperManagerTest";
+    private static final long MAX_WAIT_TIME_SECS = 2;
+    private static final long MAX_WAIT_TIME_MS = MAX_WAIT_TIME_SECS * 1000;
+    private static final long WAIT_TIME_INCR_MS = 100;
 
     private WallpaperManager mWallpaperManager;
     private Context mContext;
@@ -75,6 +79,7 @@
     private BroadcastReceiver mBroadcastReceiver;
     private CountDownLatch mCountDownLatch;
     private boolean mEnableWcg;
+    private boolean mAcquiredWallpaperDimmingPermission = false;
 
     @Before
     public void setUp() throws Exception {
@@ -106,6 +111,23 @@
         if (mBroadcastReceiver != null) {
             mContext.unregisterReceiver(mBroadcastReceiver);
         }
+        try {
+            ensureSetWallpaperDimAmountPermissionIsGranted();
+            mWallpaperManager.setWallpaperDimAmount(0f);
+            assertDimAmountEqualsTo(0f);
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+            mAcquiredWallpaperDimmingPermission = false;
+        }
+    }
+
+    private void ensureSetWallpaperDimAmountPermissionIsGranted() {
+        if (!mAcquiredWallpaperDimmingPermission) {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.SET_WALLPAPER_DIM_AMOUNT);
+            mAcquiredWallpaperDimmingPermission = true;
+        }
     }
 
     @Test
@@ -403,6 +425,95 @@
         }
     }
 
+    @Test
+    public void testGetWallpaperDimAmountWithNoPermission_shouldThrowException() {
+        Assert.assertThrows(SecurityException.class,
+                () -> mWallpaperManager.getWallpaperDimAmount());
+    }
+
+    @Test
+    public void testSetWallpaperDimAmountWithNoPermission_shouldThrowException() {
+        Assert.assertThrows(SecurityException.class,
+                () -> {
+                    float dimAmount = 0.5f;
+                    mWallpaperManager.setWallpaperDimAmount(dimAmount);
+                    assertDimAmountEqualsTo(dimAmount);
+                });
+    }
+
+    @Test
+    public void setWallpaperDimAmount_withinBound_shouldSetDimAmount() {
+        ensureSetWallpaperDimAmountPermissionIsGranted();
+
+        float dimAmount = 0.6f;
+        mWallpaperManager.setWallpaperDimAmount(dimAmount);
+        assertDimAmountEqualsTo(dimAmount);
+
+        // Remove additional dimming and verify that the dim amount is set to 0 again
+        mWallpaperManager.setWallpaperDimAmount(0f);
+        assertDimAmountEqualsTo(0f);
+    }
+
+    @Test
+    public void setWallpaperDimAmountBeyondRange_shouldBeBounded() {
+        ensureSetWallpaperDimAmountPermissionIsGranted();
+
+        // Setting dim amount < 0 should be bounded to lower limit 0.0
+        mWallpaperManager.setWallpaperDimAmount(-1f);
+        assertDimAmountEqualsTo(0f);
+
+        // Setting dim amount > 1 should be bounded to upper limit 1.0
+        mWallpaperManager.setWallpaperDimAmount(1.5f);
+        assertDimAmountEqualsTo(1f);
+    }
+
+    @Test
+    public void setWallpaperDimAmount_changingWallpaperShouldRemainDimmed() throws IOException {
+        ensureSetWallpaperDimAmountPermissionIsGranted();
+
+        float dimAmount = 0.65f;
+        mWallpaperManager.setWallpaperDimAmount(dimAmount);
+        mWallpaperManager.setResource(R.drawable.robot);
+
+        assertDimAmountEqualsTo(dimAmount);
+    }
+
+    @Test
+    public void colorHintsOnDimTest() throws IOException {
+        ensureSetWallpaperDimAmountPermissionIsGranted();
+
+        Bitmap tmpWallpaper = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(tmpWallpaper);
+        canvas.drawColor(Color.WHITE);
+
+        mWallpaperManager.setBitmap(tmpWallpaper);
+
+        WallpaperColors colors = mWallpaperManager
+                .getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+        int colorHints = colors.getColorHints();
+        // Color hints support dark text on white wallpaper
+        Assert.assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+
+        float lowDimAmount = 0.1f;
+        mWallpaperManager.setWallpaperDimAmount(lowDimAmount);
+        assertDimAmountEqualsTo(lowDimAmount);
+        colors = mWallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+        colorHints = colors.getColorHints();
+        // Color hints still support dark text on white wallpaper that is not dimmed enough
+        Assert.assertEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+
+        float higherDimAmount = 0.7f;
+        mWallpaperManager.setWallpaperDimAmount(higherDimAmount);
+        assertDimAmountEqualsTo(higherDimAmount);
+        colors = mWallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM);
+        colorHints = colors.getColorHints();
+        // Dimmed white wallpaper does not support dark text
+        Assert.assertNotEquals(WallpaperColors.HINT_SUPPORTS_DARK_TEXT,
+                colorHints & WallpaperColors.HINT_SUPPORTS_DARK_TEXT);
+    }
+
     private void assertBitmapDimensions(Bitmap bitmap) {
         int maxSize = getMaxTextureSize();
         boolean safe = false;
@@ -571,6 +682,20 @@
         return spy(new TestableColorListener());
     }
 
+    private void assertDimAmountEqualsTo(float dimAmount) {
+        float storedDimAmount = -1f;
+        for (int i = 0; i < MAX_WAIT_TIME_MS; i += WAIT_TIME_INCR_MS) {
+            storedDimAmount = mWallpaperManager.getWallpaperDimAmount();
+            if (dimAmount == storedDimAmount) break;
+            try {
+                Thread.sleep(WAIT_TIME_INCR_MS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
+        }
+        Assert.assertEquals(dimAmount, storedDimAmount, /* delta */ 0f);
+    }
+
     public class TestableColorListener implements WallpaperManager.OnColorsChangedListener {
         @Override
         public void onColorsChanged(WallpaperColors colors, int which) {
diff --git a/tests/app/src/android/app/people/cts/PeopleManagerTest.java b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
index 8b67eb4..ac6a52d 100644
--- a/tests/app/src/android/app/people/cts/PeopleManagerTest.java
+++ b/tests/app/src/android/app/people/cts/PeopleManagerTest.java
@@ -16,6 +16,9 @@
 
 package android.app.people.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.people.ConversationStatus.ACTIVITY_ANNIVERSARY;
 import static android.app.people.ConversationStatus.ACTIVITY_GAME;
@@ -38,12 +41,17 @@
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
 import android.graphics.drawable.Icon;
+import android.os.Process;
 import android.os.SystemClock;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.test.AndroidTestCase;
 import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -72,6 +80,7 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         // This will leave a set of channels on the device with each test run.
         mId = UUID.randomUUID().toString();
         mNotificationManager = mContext.getSystemService(NotificationManager.class);
@@ -105,6 +114,15 @@
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
         deleteShortcuts();
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
     }
 
     /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
diff --git a/tests/appsearch/Android.bp b/tests/appsearch/Android.bp
index 8ecb5aa..c17fa34 100644
--- a/tests/appsearch/Android.bp
+++ b/tests/appsearch/Android.bp
@@ -24,6 +24,7 @@
         "androidx.test.ext.junit",
         "androidx.test.rules",
         "compatibility-device-util-axt",
+        "service-appsearch",
         "testng",
     ],
     srcs: [
@@ -33,6 +34,7 @@
     test_suites: [
         "cts",
         "general-tests",
+        "mts-appsearch",
     ],
     platform_apis: true,
 }
@@ -49,20 +51,19 @@
     ],
     srcs: [
         "helper-app/src/**/*.java",
-        ":CtsAppSearchTestsAidl"
+        ":CtsAppSearchTestsAidl",
     ],
     test_suites: [
         "cts",
-        "vts",
-        "vts10",
         "general-tests",
+        "mts-appsearch",
     ],
     manifest: "helper-app/AndroidManifest.xml",
     aaptflags: [
         "--rename-manifest-package com.android.cts.appsearch.helper.a",
     ],
     certificate: ":cts-appsearch-helper-cert-a",
-    sdk_version: "test_current"
+    sdk_version: "test_current",
 }
 
 android_test_helper_app {
@@ -77,25 +78,24 @@
     ],
     srcs: [
         "helper-app/src/**/*.java",
-        ":CtsAppSearchTestsAidl"
+        ":CtsAppSearchTestsAidl",
     ],
     test_suites: [
         "cts",
-        "vts",
-        "vts10",
         "general-tests",
+        "mts-appsearch",
     ],
     manifest: "helper-app/AndroidManifest.xml",
     aaptflags: [
         "--rename-manifest-package com.android.cts.appsearch.helper.b",
     ],
     certificate: ":cts-appsearch-helper-cert-b",
-    sdk_version: "test_current"
+    sdk_version: "test_current",
 }
 
 filegroup {
     name: "CtsAppSearchTestsAidl",
     srcs: [
         "aidl/**/*.aidl",
-    ]
+    ],
 }
diff --git a/tests/appsearch/AndroidTest.xml b/tests/appsearch/AndroidTest.xml
index 9577159..245569d 100644
--- a/tests/appsearch/AndroidTest.xml
+++ b/tests/appsearch/AndroidTest.xml
@@ -32,4 +32,9 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.cts.appsearch" />
     </test>
+
+    <object type="module_controller"
+            class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.appsearch" />
+    </object>
 </configuration>
diff --git a/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
index c30f625..76ba9f1 100644
--- a/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
+++ b/tests/appsearch/helper-app/src/com/android/cts/appsearch/helper/AppSearchTestService.java
@@ -15,8 +15,8 @@
  */
 package com.android.cts.appsearch.helper;
 
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
 
 import android.app.Service;
 import android.app.appsearch.AppSearchManager;
@@ -27,14 +27,14 @@
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
 import android.content.Intent;
 import android.os.IBinder;
 import android.util.Log;
 
 import com.android.cts.appsearch.ICommandReceiver;
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
 
 import java.util.ArrayList;
 import java.util.Collections;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
index 2c6e032..20529fe 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSchemaMigrationCtsTest.java
@@ -17,11 +17,10 @@
 
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 
 import androidx.annotation.NonNull;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import com.google.common.util.concurrent.ListenableFuture;
 
 public class AppSearchSchemaMigrationCtsTest extends AppSearchSchemaMigrationCtsTestBase {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
index 559653cf..2efc5f1 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionCtsTest.java
@@ -17,13 +17,12 @@
 
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 import android.content.Context;
 
 import androidx.annotation.NonNull;
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import com.google.common.util.concurrent.ListenableFuture;
 
 import java.util.concurrent.ExecutorService;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
index c586d2c..c11d701 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/AppSearchSessionPlatformCtsTest.java
@@ -24,6 +24,8 @@
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.PutDocumentsRequest;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
 import android.app.usage.StorageStats;
 import android.app.usage.StorageStatsManager;
 import android.content.Context;
@@ -31,9 +33,6 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
index ecdc9c0..3664743 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionCtsTest.java
@@ -18,12 +18,11 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSessionShim;
 import android.app.appsearch.GlobalSearchSessionShim;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
 
 import androidx.annotation.NonNull;
 
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
-
 import com.google.common.util.concurrent.ListenableFuture;
 
 public class GlobalSearchSessionCtsTest extends GlobalSearchSessionCtsTestBase {
diff --git a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
index 2d5331d..b1a6511 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/app/GlobalSearchSessionPlatformCtsTest.java
@@ -16,8 +16,7 @@
 package android.app.appsearch.cts.app;
 
 import static android.Manifest.permission.READ_GLOBAL_APP_SEARCH_DATA;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -33,6 +32,9 @@
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchSessionShimImpl;
+import android.app.appsearch.testutil.GlobalSearchSessionShimImpl;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -45,9 +47,6 @@
 
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.cts.appsearch.ICommandReceiver;
-import com.android.server.appsearch.testing.AppSearchEmail;
-import com.android.server.appsearch.testing.AppSearchSessionShimImpl;
-import com.android.server.appsearch.testing.GlobalSearchSessionShimImpl;
 
 import com.google.common.collect.ImmutableSet;
 import com.google.common.io.BaseEncoding;
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
index 6dd9158..4661354 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaCtsTest.java
@@ -23,8 +23,7 @@
 import android.app.appsearch.AppSearchSchema;
 import android.app.appsearch.AppSearchSchema.PropertyConfig;
 import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
-
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchEmail;
 
 import org.junit.Test;
 
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
index a87a1ac..af62872 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSchemaMigrationCtsTestBase.java
@@ -17,10 +17,9 @@
 package android.app.appsearch.cts.app;
 
 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
index d30d661..f78b2a6 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/AppSearchSessionCtsTestBase.java
@@ -18,11 +18,10 @@
 
 import static android.app.appsearch.AppSearchResult.RESULT_INVALID_SCHEMA;
 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
-
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.doGet;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.retrieveAllSearchResults;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.doGet;
+import static android.app.appsearch.testutil.AppSearchTestUtils.retrieveAllSearchResults;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -35,6 +34,7 @@
 import android.app.appsearch.AppSearchSchema.PropertyConfig;
 import android.app.appsearch.AppSearchSchema.StringPropertyConfig;
 import android.app.appsearch.AppSearchSessionShim;
+import android.app.appsearch.Features;
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GetByDocumentIdRequest;
 import android.app.appsearch.GetSchemaResponse;
@@ -47,12 +47,11 @@
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.StorageInfo;
 import android.app.appsearch.exceptions.AppSearchException;
+import android.app.appsearch.testutil.AppSearchEmail;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.server.appsearch.testing.AppSearchEmail;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.MoreExecutors;
@@ -70,8 +69,8 @@
 import java.util.concurrent.ExecutorService;
 
 public abstract class AppSearchSessionCtsTestBase {
-    private static final String DB_NAME_1 = "";
-    private static final String DB_NAME_2 = "testDb2";
+    static final String DB_NAME_1 = "";
+    static final String DB_NAME_2 = "testDb2";
 
     private AppSearchSessionShim mDb1;
     private AppSearchSessionShim mDb2;
@@ -1254,6 +1253,17 @@
         documents = convertSearchResultsToDocuments(searchResults);
         assertThat(documents).hasSize(1);
         assertThat(documents).containsExactly(inDoc);
+
+        // Query only for non-exist type
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas("nonExistType")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
     }
 
     @Test
@@ -1347,6 +1357,17 @@
         documents = convertSearchResultsToDocuments(searchResults);
         assertThat(documents).hasSize(1);
         assertThat(documents).containsExactly(expectedEmail);
+
+        // Query only for non-exist namespace
+        searchResults =
+                mDb1.search(
+                        "body",
+                        new SearchSpec.Builder()
+                                .addFilterNamespaces("nonExistNamespace")
+                                .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
+                                .build());
+        documents = convertSearchResultsToDocuments(searchResults);
+        assertThat(documents).isEmpty();
     }
 
     @Test
@@ -2042,7 +2063,7 @@
         // Query for the document
         SearchResultsShim searchResults =
                 mDb1.search(
-                        "foo",
+                        "fo",
                         new SearchSpec.Builder()
                                 .addFilterSchemas("Generic")
                                 .setSnippetCount(1)
@@ -2067,6 +2088,15 @@
         assertThat(matchInfo.getSnippetRange())
                 .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 26, /*upper=*/ 33));
         assertThat(matchInfo.getSnippet()).isEqualTo("is foo.");
+
+        if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
+            assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatchRange());
+            assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatch());
+        } else {
+            assertThat(matchInfo.getSubmatchRange())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 29, /*upper=*/ 31));
+            assertThat(matchInfo.getSubmatch()).isEqualTo("fo");
+        }
     }
 
     @Test
@@ -2205,6 +2235,15 @@
         assertThat(matchInfo.getExactMatchRange())
                 .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
         assertThat(matchInfo.getExactMatch()).isEqualTo("は");
+
+        if (!mDb1.getFeatures().isFeatureSupported(Features.SEARCH_RESULT_MATCH_INFO_SUBMATCH)) {
+            assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatchRange());
+            assertThrows(UnsupportedOperationException.class, () -> matchInfo.getSubmatch());
+        } else {
+            assertThat(matchInfo.getSubmatchRange())
+                    .isEqualTo(new SearchResult.MatchRange(/*lower=*/ 44, /*upper=*/ 45));
+            assertThat(matchInfo.getSubmatch()).isEqualTo("は");
+        }
     }
 
     @Test
@@ -2383,6 +2422,50 @@
     }
 
     @Test
+    public void testRemoveByQuery_nonExistNamespace() throws Exception {
+        // Schema registration
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Index documents
+        AppSearchEmail email1 =
+                new AppSearchEmail.Builder("namespace1", "id1")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("foo")
+                        .setBody("This is the body of the testPut email")
+                        .build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("namespace2", "id2")
+                        .setFrom("from@example.com")
+                        .setTo("to1@example.com", "to2@example.com")
+                        .setSubject("bar")
+                        .setBody("This is the body of the testPut second email")
+                        .build();
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2)
+                                .build()));
+
+        // Check the presence of the documents
+        assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
+        assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
+
+        // Delete the email by nonExist namespace.
+        mDb1.remove(
+                        "",
+                        new SearchSpec.Builder()
+                                .setTermMatch(SearchSpec.TERM_MATCH_PREFIX)
+                                .addFilterNamespaces("nonExistNamespace")
+                                .build())
+                .get();
+        // None of these emails will be deleted.
+        assertThat(doGet(mDb1, "namespace1", "id1")).hasSize(1);
+        assertThat(doGet(mDb1, "namespace2", "id2")).hasSize(1);
+    }
+
+    @Test
     public void testRemoveByQuery_packageFilter() throws Exception {
         // Schema registration
         mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
index 70f9b48..0dfff00 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GenericDocumentCtsTest.java
@@ -332,9 +332,19 @@
     public void testDocument_setEmptyValues() {
         GenericDocument document =
                 new GenericDocument.Builder<>("namespace", "id1", "schemaType1")
-                        .setPropertyBoolean("testKey")
+                        .setPropertyBoolean("booleanKey")
+                        .setPropertyString("stringKey")
+                        .setPropertyBytes("byteKey")
+                        .setPropertyDouble("doubleKey")
+                        .setPropertyDocument("documentKey")
+                        .setPropertyLong("longKey")
                         .build();
-        assertThat(document.getPropertyBooleanArray("testKey")).isEmpty();
+        assertThat(document.getPropertyBooleanArray("booleanKey")).isEmpty();
+        assertThat(document.getPropertyStringArray("stringKey")).isEmpty();
+        assertThat(document.getPropertyBytesArray("byteKey")).isEmpty();
+        assertThat(document.getPropertyDoubleArray("doubleKey")).isEmpty();
+        assertThat(document.getPropertyDocumentArray("documentKey")).isEmpty();
+        assertThat(document.getPropertyLongArray("longKey")).isEmpty();
     }
 
     @Test
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
index 7dbe8d9..bcae910 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/GlobalSearchSessionCtsTestBase.java
@@ -16,8 +16,8 @@
 
 package android.app.appsearch.cts.app;
 
-import static com.android.server.appsearch.testing.AppSearchTestUtils.checkIsBatchResultSuccess;
-import static com.android.server.appsearch.testing.AppSearchTestUtils.convertSearchResultsToDocuments;
+import static android.app.appsearch.testutil.AppSearchTestUtils.checkIsBatchResultSuccess;
+import static android.app.appsearch.testutil.AppSearchTestUtils.convertSearchResultsToDocuments;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -31,18 +31,21 @@
 import android.app.appsearch.GenericDocument;
 import android.app.appsearch.GlobalSearchSessionShim;
 import android.app.appsearch.PutDocumentsRequest;
+import android.app.appsearch.RemoveByDocumentIdRequest;
 import android.app.appsearch.ReportSystemUsageRequest;
 import android.app.appsearch.SearchResult;
 import android.app.appsearch.SearchResultsShim;
 import android.app.appsearch.SearchSpec;
 import android.app.appsearch.SetSchemaRequest;
 import android.app.appsearch.exceptions.AppSearchException;
+import android.app.appsearch.observer.DocumentChangeInfo;
+import android.app.appsearch.observer.ObserverSpec;
+import android.app.appsearch.testutil.AppSearchEmail;
+import android.app.appsearch.testutil.TestObserverCallback;
 import android.content.Context;
 
 import androidx.test.core.app.ApplicationProvider;
 
-import com.android.server.appsearch.testing.AppSearchEmail;
-
 import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -54,14 +57,20 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 public abstract class GlobalSearchSessionCtsTestBase {
-    private AppSearchSessionShim mDb1;
-    private static final String DB_NAME_1 = "";
-    private AppSearchSessionShim mDb2;
-    private static final String DB_NAME_2 = "testDb2";
+    static final String DB_NAME_1 = "";
+    static final String DB_NAME_2 = "testDb2";
 
-    private GlobalSearchSessionShim mGlobalAppSearchManager;
+    private static final Executor EXECUTOR = Executors.newCachedThreadPool();
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+
+    private AppSearchSessionShim mDb1;
+    private AppSearchSessionShim mDb2;
+
+    private GlobalSearchSessionShim mGlobalSearchSession;
 
     protected abstract ListenableFuture<AppSearchSessionShim> createSearchSession(
             @NonNull String dbName);
@@ -70,8 +79,6 @@
 
     @Before
     public void setUp() throws Exception {
-        Context context = ApplicationProvider.getApplicationContext();
-
         mDb1 = createSearchSession(DB_NAME_1).get();
         mDb2 = createSearchSession(DB_NAME_2).get();
 
@@ -79,7 +86,7 @@
         // addition to tearDown in case a test exited without completing properly.
         cleanup();
 
-        mGlobalAppSearchManager = createGlobalSearchSession().get();
+        mGlobalSearchSession = createGlobalSearchSession().get();
     }
 
     @After
@@ -95,7 +102,7 @@
 
     private List<GenericDocument> snapshotResults(String queryExpression, SearchSpec spec)
             throws Exception {
-        SearchResultsShim searchResults = mGlobalAppSearchManager.search(queryExpression, spec);
+        SearchResultsShim searchResults = mGlobalSearchSession.search(queryExpression, spec);
         return convertSearchResultsToDocuments(searchResults);
     }
 
@@ -225,7 +232,7 @@
         // Set number of results per page is 7.
         int pageSize = 7;
         SearchResultsShim searchResults =
-                mGlobalAppSearchManager.search(
+                mGlobalSearchSession.search(
                         "body",
                         new SearchSpec.Builder()
                                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
@@ -406,8 +413,7 @@
         SearchSpec testPackageSearchSpec =
                 new SearchSpec.Builder()
                         .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
-                        .addFilterPackageNames(
-                                ApplicationProvider.getApplicationContext().getPackageName())
+                        .addFilterPackageNames(mContext.getPackageName())
                         .build();
         List<GenericDocument> beforeTestPackageDocuments =
                 snapshotResults("body", testPackageSearchSpec);
@@ -732,7 +738,7 @@
         // Query
         List<SearchResult> page;
         try (SearchResultsShim results =
-                mGlobalAppSearchManager.search(
+                mGlobalSearchSession.search(
                         "",
                         new SearchSpec.Builder()
                                 .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY)
@@ -747,7 +753,7 @@
                 assertThrows(
                         ExecutionException.class,
                         () ->
-                                mGlobalAppSearchManager
+                                mGlobalSearchSession
                                         .reportSystemUsage(
                                                 new ReportSystemUsageRequest.Builder(
                                                                 firstResult.getPackageName(),
@@ -767,4 +773,364 @@
                 .hasMessageThat()
                 .contains("com.android.cts.appsearch does not have access to report system usage");
     }
+
+    @Test
+    public void testRegisterObserver() throws Exception {
+        TestObserverCallback observer = new TestObserverCallback();
+
+        // Register observer. Note: the type does NOT exist yet!
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+                EXECUTOR,
+                observer);
+
+        // Index a document
+        mDb1.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+
+        // Make sure the notification was received.
+        observer.waitForNotificationCount(1);
+        assertThat(observer.getSchemaChanges()).isEmpty();
+        assertThat(observer.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE));
+    }
+
+    @Test
+    public void testRegisterObserver_MultiType() throws Exception {
+        TestObserverCallback unfilteredObserver = new TestObserverCallback();
+        TestObserverCallback emailObserver = new TestObserverCallback();
+
+        // Set up the email type in both databases, and the gift type in db1
+        AppSearchSchema giftSchema =
+                new AppSearchSchema.Builder("Gift")
+                        .addProperty(
+                                new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+                                .build())
+                .get();
+        mDb2.setSchema(new SetSchemaRequest.Builder().addSchemas(AppSearchEmail.SCHEMA).build())
+                .get();
+
+        // Register two observers. One has no filters, the other filters on email.
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().build(),
+                EXECUTOR,
+                unfilteredObserver);
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+                EXECUTOR,
+                emailObserver);
+
+        // Make sure everything is empty
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+        // Index some documents
+        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+        GenericDocument gift1 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id2", "Gift")
+                        .build();
+
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, gift1)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift1).build()));
+
+        // Make sure the notification was received.
+        unfilteredObserver.waitForNotificationCount(5);
+        emailObserver.waitForNotificationCount(3);
+
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(), DB_NAME_1, "namespace2", "Gift"),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_2,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(), DB_NAME_1, "namespace2", "Gift"));
+
+        // Check the filtered observer
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_2,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE));
+    }
+
+    @Test
+    public void testRegisterObserver_removeById() throws Exception {
+        TestObserverCallback unfilteredObserver = new TestObserverCallback();
+        TestObserverCallback emailObserver = new TestObserverCallback();
+
+        // Set up the email and gift types in both databases
+        AppSearchSchema giftSchema =
+                new AppSearchSchema.Builder("Gift")
+                        .addProperty(
+                                new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+                                .build())
+                .get();
+        mDb2.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+                                .build())
+                .get();
+
+        // Register two observers. One, registered later, has no filters. The other, registered
+        // now, filters on email.
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+                EXECUTOR,
+                emailObserver);
+
+        // Make sure everything is empty
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+        // Index some documents
+        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+        GenericDocument gift1 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id2", "Gift")
+                        .build();
+
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(email1).build()));
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, gift1)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, gift1)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb1.put(new PutDocumentsRequest.Builder().addGenericDocuments(gift1).build()));
+
+        // Register the second observer
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().build(),
+                EXECUTOR,
+                unfilteredObserver);
+
+        // Remove some of the documents.
+        checkIsBatchResultSuccess(
+                mDb1.remove(
+                        new RemoveByDocumentIdRequest.Builder("namespace").addIds("id1").build()));
+        checkIsBatchResultSuccess(
+                mDb2.remove(
+                        new RemoveByDocumentIdRequest.Builder("namespace2").addIds("id2").build()));
+
+        // Make sure the notification was received. emailObserver should have seen:
+        //   +db1:email, +db1:email, +db2:email, -db1:email.
+        // unfilteredObserver (registered later) should have seen:
+        //   -db1:email, -db2:gift
+        emailObserver.waitForNotificationCount(4);
+        unfilteredObserver.waitForNotificationCount(2);
+
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_2,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE));
+
+        // Check unfilteredObserver
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(), DB_NAME_2, "namespace2", "Gift"));
+    }
+
+    @Test
+    public void testRegisterObserver_removeByQuery() throws Exception {
+        TestObserverCallback unfilteredObserver = new TestObserverCallback();
+        TestObserverCallback emailObserver = new TestObserverCallback();
+
+        // Set up the email and gift types in both databases
+        AppSearchSchema giftSchema =
+                new AppSearchSchema.Builder("Gift")
+                        .addProperty(
+                                new AppSearchSchema.DoublePropertyConfig.Builder("price").build())
+                        .build();
+        mDb1.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+                                .build())
+                .get();
+        mDb2.setSchema(
+                        new SetSchemaRequest.Builder()
+                                .addSchemas(AppSearchEmail.SCHEMA, giftSchema)
+                                .build())
+                .get();
+
+        // Index some documents
+        AppSearchEmail email1 = new AppSearchEmail.Builder("namespace", "id1").build();
+        AppSearchEmail email2 =
+                new AppSearchEmail.Builder("namespace", "id2").setBody("caterpillar").build();
+        GenericDocument gift1 =
+                new GenericDocument.Builder<GenericDocument.Builder<?>>("namespace2", "id3", "Gift")
+                        .build();
+
+        checkIsBatchResultSuccess(
+                mDb1.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2, gift1)
+                                .build()));
+        checkIsBatchResultSuccess(
+                mDb2.put(
+                        new PutDocumentsRequest.Builder()
+                                .addGenericDocuments(email1, email2, gift1)
+                                .build()));
+
+        // Register observers
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().build(),
+                EXECUTOR,
+                unfilteredObserver);
+        mGlobalSearchSession.addObserver(
+                mContext.getPackageName(),
+                new ObserverSpec.Builder().addFilterSchemas(AppSearchEmail.SCHEMA_TYPE).build(),
+                EXECUTOR,
+                emailObserver);
+
+        // Make sure everything is empty
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges()).isEmpty();
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges()).isEmpty();
+
+        // Remove "cat" emails in db1 and all types in db2
+        mDb1.remove(
+                        "cat",
+                        new SearchSpec.Builder()
+                                .addFilterSchemas(AppSearchEmail.SCHEMA_TYPE)
+                                .build())
+                .get();
+        mDb2.remove("", new SearchSpec.Builder().build()).get();
+
+        // Make sure the notification was received. UnfilteredObserver should have seen:
+        //   -db1:id2, -db2:id1, -db2:id2, -db2:id3
+        // emailObserver should have seen:
+        //   -db1:id2, -db2:id1, -db2:id2
+        // TODO(b/193494000): Notifications are currently grouped by
+        //  (package, database, namespace, schema). This causes -db2:id1 and -db2:id2 to be combined
+        //  into one notification. Once notifications have IDs, we need to check to make sure all
+        //  the individual IDs are reported in the combined notification.
+        unfilteredObserver.waitForNotificationCount(3);
+        emailObserver.waitForNotificationCount(2);
+
+        assertThat(unfilteredObserver.getSchemaChanges()).isEmpty();
+        assertThat(unfilteredObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_2,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(), DB_NAME_2, "namespace2", "Gift"));
+
+        // Check emailObserver
+        assertThat(emailObserver.getSchemaChanges()).isEmpty();
+        assertThat(emailObserver.getDocumentChanges())
+                .containsExactly(
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_1,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE),
+                        new DocumentChangeInfo(
+                                mContext.getPackageName(),
+                                DB_NAME_2,
+                                "namespace",
+                                AppSearchEmail.SCHEMA_TYPE));
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
index b42d4a6..bc90e2f 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/PutDocumentsRequestCtsTest.java
@@ -20,8 +20,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.appsearch.PutDocumentsRequest;
-
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.testutil.AppSearchEmail;
 
 import com.google.common.collect.ImmutableSet;
 
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
index 588210b..fa580ab 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/SearchResultCtsTest.java
@@ -18,9 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.app.appsearch.SearchResult;
+import static org.junit.Assert.assertThrows;
 
-import com.android.server.appsearch.testing.AppSearchEmail;
+import android.app.appsearch.SearchResult;
+import android.app.appsearch.testutil.AppSearchEmail;
 
 import org.junit.Test;
 
@@ -29,10 +30,12 @@
     @Test
     public void testBuildSearchResult() {
         SearchResult.MatchRange exactMatchRange = new SearchResult.MatchRange(3, 8);
+        SearchResult.MatchRange submatchRange = new SearchResult.MatchRange(3, 5);
         SearchResult.MatchRange snippetMatchRange = new SearchResult.MatchRange(1, 10);
         SearchResult.MatchInfo matchInfo =
                 new SearchResult.MatchInfo.Builder("body")
                         .setExactMatchRange(exactMatchRange)
+                        .setSubmatchRange(submatchRange)
                         .setSnippetRange(snippetMatchRange)
                         .build();
 
@@ -53,8 +56,10 @@
         SearchResult.MatchInfo actualMatchInfo = searchResult.getMatchInfos().get(0);
         assertThat(actualMatchInfo.getPropertyPath()).isEqualTo("body");
         assertThat(actualMatchInfo.getExactMatchRange()).isEqualTo(exactMatchRange);
+        assertThat(actualMatchInfo.getSubmatchRange()).isEqualTo(submatchRange);
         assertThat(actualMatchInfo.getSnippetRange()).isEqualTo(snippetMatchRange);
         assertThat(actualMatchInfo.getExactMatch()).isEqualTo("lo Wo");
+        assertThat(actualMatchInfo.getSubmatch()).isEqualTo("lo");
         assertThat(actualMatchInfo.getSnippet()).isEqualTo("ello Worl");
         assertThat(actualMatchInfo.getFullText()).isEqualTo("Hello World.");
     }
@@ -65,4 +70,39 @@
         assertThat(matchRange.getStart()).isEqualTo(13);
         assertThat(matchRange.getEnd()).isEqualTo(47);
     }
+
+    @Test
+    public void testSubmatchRangeNotSet() {
+        AppSearchEmail email =
+                new AppSearchEmail.Builder("namespace1", "id1").setBody("Hello World.").build();
+        SearchResult.MatchInfo matchInfo = new SearchResult.MatchInfo.Builder("body").build();
+        SearchResult searchResult =
+                new SearchResult.Builder("packageName", "databaseName")
+                        .setGenericDocument(email)
+                        .addMatchInfo(matchInfo)
+                        .build();
+
+        // When submatch isn't set, calling getSubmatch and getSubmatchRange should throw.
+        final SearchResult.MatchInfo actualMatchInfoNoSubmatch =
+                searchResult.getMatchInfos().get(0);
+        assertThrows(
+                UnsupportedOperationException.class, () -> actualMatchInfoNoSubmatch.getSubmatch());
+        assertThrows(
+                UnsupportedOperationException.class,
+                () -> actualMatchInfoNoSubmatch.getSubmatchRange());
+
+        // When submatch is set, calling getSubmatch and getSubmatchRange should return the
+        // submatch without any problems.
+        SearchResult.MatchRange submatchRange = new SearchResult.MatchRange(3, 5);
+        matchInfo =
+                new SearchResult.MatchInfo.Builder("body").setSubmatchRange(submatchRange).build();
+        searchResult =
+                new SearchResult.Builder("packageName", "databaseName")
+                        .setGenericDocument(email)
+                        .addMatchInfo(matchInfo)
+                        .build();
+        final SearchResult.MatchInfo actualMatchInfo = searchResult.getMatchInfos().get(0);
+        assertThat(actualMatchInfo.getSubmatch()).isEqualTo("lo");
+        assertThat(actualMatchInfo.getSubmatchRange()).isEqualTo(submatchRange);
+    }
 }
diff --git a/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java b/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
index 9ae7e60..d9b923d 100644
--- a/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
+++ b/tests/appsearch/src/com/android/cts/appsearch/external/app/SetSchemaRequestCtsTest.java
@@ -27,10 +27,9 @@
 import android.app.appsearch.Migrator;
 import android.app.appsearch.PackageIdentifier;
 import android.app.appsearch.SetSchemaRequest;
+import android.app.appsearch.testutil.AppSearchEmail;
 import android.util.ArrayMap;
 
-import com.android.server.appsearch.testing.AppSearchEmail;
-
 import org.junit.Test;
 
 import java.util.Arrays;
diff --git a/tests/atv/OWNERS b/tests/atv/OWNERS
new file mode 100644
index 0000000..02fada1
--- /dev/null
+++ b/tests/atv/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 105709
+dwliu@google.com
+jingjiangli@google.com
+zhizhiliu@google.com
diff --git a/tests/atv/SettingsAPI/Android.bp b/tests/atv/SettingsAPI/Android.bp
new file mode 100644
index 0000000..04c7c48
--- /dev/null
+++ b/tests/atv/SettingsAPI/Android.bp
@@ -0,0 +1,47 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSettingsAPITestCases",
+    defaults: ["cts_defaults"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    static_libs: [
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "truth-prebuilt",
+        "TvSettingsAPI"
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    min_sdk_version: "31",
+}
+
diff --git a/tests/atv/SettingsAPI/AndroidManifest.xml b/tests/atv/SettingsAPI/AndroidManifest.xml
new file mode 100644
index 0000000..6cdb447
--- /dev/null
+++ b/tests/atv/SettingsAPI/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="com.android.tv.settings.library.cts">
+    <uses-sdk android:minSdkVersion="31" />
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:label="CTS TV Settings API tests"
+         android:targetPackage="com.android.tv.settings.library.cts">
+    </instrumentation>
+</manifest>
diff --git a/tests/atv/SettingsAPI/AndroidTest.xml b/tests/atv/SettingsAPI/AndroidTest.xml
new file mode 100644
index 0000000..0f5f1c6
--- /dev/null
+++ b/tests/atv/SettingsAPI/AndroidTest.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.
+  -->
+<configuration description="Config for CTS TV Settings API 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" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSettingsAPITestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.tv.settings.library.cts" />
+    </test>
+</configuration>
diff --git a/tests/atv/SettingsAPI/OWNERS b/tests/atv/SettingsAPI/OWNERS
new file mode 100644
index 0000000..bfa0720
--- /dev/null
+++ b/tests/atv/SettingsAPI/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 105709
+dwliu@google.com
+jingjiangli@google.com
+zhizhiliu@google.com
\ No newline at end of file
diff --git a/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java b/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java
new file mode 100644
index 0000000..b026a5d
--- /dev/null
+++ b/tests/atv/SettingsAPI/src/com/android/tv/settings/library/cts/ManagerUtilTest.java
@@ -0,0 +1,39 @@
+/*
+ * 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.tv.settings.library.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tv.settings.library.ManagerUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class ManagerUtilTest {
+    // TODO(b/187659818) Add test cases for ManagerUtil.
+    @Test
+    public void testGetChecked() {
+        // TODO(b/187659818) Implement test case for the function
+        //  testGetChecked.
+    }
+}
\ No newline at end of file
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index 28d4ebd..58dd553 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -149,6 +149,7 @@
         <activity android:name=".activities.SimpleAfterLoginActivity"/>
         <activity android:name=".activities.SimpleBeforeLoginActivity"/>
         <activity android:name=".activities.NonAutofillableActivity"/>
+        <activity android:name=".activities.ClientSuggestionsActivity"/>
 
         <receiver android:name=".testcore.SelfDestructReceiver"
              android:exported="true"
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
index 0a32981..199e1b1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AuthenticationActivity.java
@@ -291,7 +291,8 @@
                         (id) -> Helper.findNodeByResourceId(structure, id));
             }
         } else if (dataset != null) {
-            result = dataset.asDataset((id) -> Helper.findNodeByResourceId(structure, id));
+            result = dataset.asDatasetWithNodeResolver(
+                    (id) -> Helper.findNodeByResourceId(structure, id));
         } else {
             throw new IllegalStateException("no dataset or response");
         }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.java
new file mode 100644
index 0000000..f69aa02
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ClientSuggestionsActivity.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 android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.Helper;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.View;
+import android.view.autofill.AutofillId;
+
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/**
+ * Activity that has the following fields:
+ *
+ * <ul>
+ *   <li>Username EditText (id: username, no input-type)
+ *   <li>Password EditText (id: "username", input-type textPassword)
+ *   <li>Clear Button
+ *   <li>Save Button
+ *   <li>Login Button
+ * </ul>
+ */
+public class ClientSuggestionsActivity extends LoginActivity {
+    private static final String TAG = "ClientSuggestionsActivity";
+    private Handler mHandler;
+    ClientAutofillRequestCallback mRequestCallback;
+
+    private final Map<String, AutofillId> mMap = new ArrayMap<>();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mHandler = new Handler(Looper.myLooper());
+
+        mMap.put(Helper.ID_USERNAME, findViewById(R.id.username).getAutofillId());
+        mMap.put(Helper.ID_PASSWORD, findViewById(R.id.password).getAutofillId());
+        mHandler = new Handler(Looper.getMainLooper());
+        Executor executor = new Executor(){
+            @Override
+            public void execute(Runnable command) {
+                mHandler.post(command);
+            }
+        };
+        mRequestCallback = new ClientAutofillRequestCallback(mHandler, (id)-> mMap.get(id));
+        getAutofillManager().setAutofillRequestCallback(executor, mRequestCallback);
+    }
+
+    @Override
+    public void addChild(View child) {
+        throw new AssertionError("Uses addChild(View, String) instead");
+    }
+
+    public void addChild(View child, String id) {
+        Log.d(TAG, "addChild(" + child + "): id=" + child.getAutofillId());
+        super.addChild(child);
+
+        mMap.put(id, child.getAutofillId());
+    }
+
+    public ClientAutofillRequestCallback.Replier getReplier() {
+        return mRequestCallback.getReplier();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
index 58b67d7..e3ebd1f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/ManualAuthenticationActivity.java
@@ -58,7 +58,7 @@
                     result = sResponse.asFillResponse(/* contexts= */ null,
                             (id) -> Helper.findNodeByResourceId(structure, id));
                 } else if (sDataset != null) {
-                    result = sDataset.asDataset(
+                    result = sDataset.asDatasetWithNodeResolver(
                             (id) -> Helper.findNodeByResourceId(structure, id));
                 } else {
                     throw new IllegalStateException("no dataset or response");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
index 22587d2..2d0fdf9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/OptionalSaveActivity.java
@@ -105,11 +105,7 @@
         finish();
     }
 
-    /**
-     * Sets the expectation for an auto-fill request, so it can be asserted through
-     * {@link #assertAutoFilled()} later.
-     */
-    public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+    public void expectTextChange(@Nullable String address1, @Nullable String address2,
             @Nullable String city,
             @Nullable String favColor) {
         mExpectation = new FillExpectation(address1, address2, city, favColor);
@@ -128,11 +124,16 @@
     }
 
     /**
-     * Asserts the activity was auto-filled with the values passed to
-     * {@link #expectAutoFill(String, String, String, String)}.
+     * Sets the expectation for an auto-fill request, so it can be asserted through
+     * {@link #assertAutoFilled()} later.
      */
-    public void assertAutoFilled() throws Exception {
-        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+    public void expectAutoFill(@Nullable String address1, @Nullable String address2,
+            @Nullable String city,
+            @Nullable String favColor) {
+        expectTextChange(address1, address2, city, favColor);
+    }
+
+    public void assertTextChange() throws Exception {
         if (mExpectation.address1Watcher != null) {
             mExpectation.address1Watcher.assertAutoFilled();
         }
@@ -148,6 +149,15 @@
     }
 
     /**
+     * Asserts the activity was auto-filled with the values passed to
+     * {@link #expectAutoFill(String, String, String, String)}.
+     */
+    public void assertAutoFilled() throws Exception {
+        assertWithMessage("expectAutoFill() not called").that(mExpectation).isNotNull();
+        assertTextChange();
+    }
+
+    /**
      * Holder for the expected auto-fill values.
      */
     private final class FillExpectation {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
index 02881cb..ab0b619 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/PreSimpleSaveActivity.java
@@ -61,12 +61,25 @@
         });
     }
 
-    public FillExpectation expectAutoFill(String input) {
+    /**
+     * Set the EditText input or password value and wait until text change.
+     */
+    public void setTextAndWaitTextChange(String input) throws Exception {
+        final FillExpectation expectation = expectInputTextChange(input);
+        syncRunOnUiThread(() -> mPreInput.setText(input));
+        expectation.assertTextChange();
+    }
+
+    public FillExpectation expectInputTextChange(String input) {
         final FillExpectation expectation = new FillExpectation(input);
         mPreInput.addTextChangedListener(expectation.mInputWatcher);
         return expectation;
     }
 
+    public FillExpectation expectAutoFill(String input) {
+        return expectInputTextChange(input);
+    }
+
     public EditText getPreInput() {
         return mPreInput;
     }
@@ -78,6 +91,10 @@
             mInputWatcher = new OneTimeTextWatcher("input", mPreInput, input);
         }
 
+        public void assertTextChange() throws Exception {
+            mInputWatcher.assertAutoFilled();
+        }
+
         public void assertAutoFilled() throws Exception {
             mInputWatcher.assertAutoFilled();
         }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
index 2866cbc..6f8f8d6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/SimpleSaveActivity.java
@@ -105,21 +105,41 @@
         mClearFieldsOnSubmit = flag;
     }
 
-    public FillExpectation expectInputTextChange(String input) {
+    /**
+     * Set the EditText input or password value and wait until text change.
+     */
+    public void setTextAndWaitTextChange(String input, String password) throws Exception {
+        FillExpectation changeExpectation = expectInputPasswordTextChange(input, password);
+        syncRunOnUiThread(() -> {
+            if (input != null) {
+                mInput.setText(input);
+            }
+            if (password != null) {
+                mPassword.setText(password);
+            }
+        });
+        changeExpectation.assertTextChange();
+    }
+
+    public FillExpectation expectAutoFill(String input) {
         final FillExpectation expectation = new FillExpectation(input, null);
         mInput.addTextChangedListener(expectation.mInputWatcher);
         return expectation;
     }
 
-    public FillExpectation expectAutoFill(String input) {
-        return expectInputTextChange(input);
+    public FillExpectation expectInputPasswordTextChange(String input, String password) {
+        final FillExpectation expectation = new FillExpectation(input, password);
+        if (expectation.mInputWatcher != null) {
+            mInput.addTextChangedListener(expectation.mInputWatcher);
+        }
+        if (expectation.mPasswordWatcher != null) {
+            mPassword.addTextChangedListener(expectation.mPasswordWatcher);
+        }
+        return expectation;
     }
 
     public FillExpectation expectAutoFill(String input, String password) {
-        final FillExpectation expectation = new FillExpectation(input, password);
-        mInput.addTextChangedListener(expectation.mInputWatcher);
-        mPassword.addTextChangedListener(expectation.mPasswordWatcher);
-        return expectation;
+        return expectInputPasswordTextChange(input, password);
     }
 
     public EditText getInput() {
@@ -131,7 +151,9 @@
         private final OneTimeTextWatcher mPasswordWatcher;
 
         private FillExpectation(String input, String password) {
-            mInputWatcher = new OneTimeTextWatcher("input", mInput, input);
+            mInputWatcher = input == null
+                    ? null
+                    : new OneTimeTextWatcher("input", mInput, input);
             mPasswordWatcher = password == null
                     ? null
                     : new OneTimeTextWatcher("password", mPassword, password);
@@ -142,7 +164,9 @@
         }
 
         public void assertAutoFilled() throws Exception {
-            mInputWatcher.assertAutoFilled();
+            if (mInputWatcher != null) {
+                mInputWatcher.assertAutoFilled();
+            }
             if (mPasswordWatcher != null) {
                 mPasswordWatcher.assertAutoFilled();
             }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java
new file mode 100644
index 0000000..1419704
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsInlineTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.autofillservice.cts.client;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.autofillservice.cts.commontests.ClientSuggestionsCommonTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.os.Bundle;
+
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+
+/**
+ * Tests client suggestions behaviors for the inline mode.
+ */
+public class ClientSuggestionsInlineTest extends ClientSuggestionsCommonTestCase {
+
+    public ClientSuggestionsInlineTest() {
+        super(getInlineUiBot());
+    }
+
+    @Override
+    protected boolean isInlineMode() {
+        return true;
+    }
+
+    @Test
+    public void testImeDisableClientSuggestions_showDropdownUi() throws Exception {
+        // Set service.
+        enableService();
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        // Disable inline suggestions for the client.
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("ClientSuggestions", false);
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        // Set expectations.
+        mClientReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Check that no inline requests are sent to the client.
+        final ClientAutofillRequestCallback.FillRequest clientRequest =
+                mClientReplier.getNextFillRequest();
+        assertThat(clientRequest.inlineRequest).isNull();
+
+        // Check dropdown UI shown.
+        getDropdownUiBot().assertDatasets("The Dude");
+    }
+
+    @Test
+    public void testImeDisableClientSuggestions_fallbackThenShowInline() throws Exception {
+        // Set service.
+        enableService();
+
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        // Disable inline suggestions for the client.
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("ClientSuggestions", false);
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Check that no inline requests are sent to the client.
+        final ClientAutofillRequestCallback.FillRequest clientRequest =
+                mClientReplier.getNextFillRequest();
+        assertThat(clientRequest.inlineRequest).isNull();
+
+        // Check that the inline request is sent to the service.
+        final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertThat(fillRequest.inlineRequest).isNotNull();
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testImeDisableServiceSuggestions_fallbackThenShowDropdownUi() throws Exception {
+        // Set service.
+        enableService();
+
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        // Disable inline suggestions for the default service.
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("ServiceSuggestions", false);
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        // Set expectations.
+        sReplier.addResponse(new CannedFillResponse.CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        // Check that the inline request is sent to the client.
+        final ClientAutofillRequestCallback.FillRequest clientRequest =
+                mClientReplier.getNextFillRequest();
+        assertThat(clientRequest.inlineRequest).isNotNull();
+
+        // Check that no inline requests are sent to the service.
+        final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+        assertThat(fillRequest.inlineRequest).isNull();
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset on the dropdown UI.
+        getDropdownUiBot().selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.java b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.java
new file mode 100644
index 0000000..895ec3a
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/client/ClientSuggestionsTest.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.autofillservice.cts.client;
+
+import android.autofillservice.cts.commontests.ClientSuggestionsCommonTestCase;
+
+/**
+ * Tests client suggestions behaviors for the dropdown mode.
+ */
+public class ClientSuggestionsTest extends ClientSuggestionsCommonTestCase {
+
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java
new file mode 100644
index 0000000..e39067f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/ClientSuggestionsCommonTestCase.java
@@ -0,0 +1,311 @@
+/*
+ * 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.autofillservice.cts.commontests;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.NO_RESPONSE;
+import static android.autofillservice.cts.testcore.Helper.ID_EMPTY;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+
+import android.autofillservice.cts.R;
+import android.autofillservice.cts.activities.ClientSuggestionsActivity;
+import android.autofillservice.cts.testcore.AutofillActivityTestRule;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.ClientAutofillRequestCallback;
+import android.autofillservice.cts.testcore.OneTimeTextWatcher;
+import android.autofillservice.cts.testcore.UiBot;
+import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
+import android.widget.EditText;
+
+import androidx.annotation.NonNull;
+
+import org.junit.Test;
+
+/**
+ * This is the test case covering most scenarios - other test cases will cover characteristics
+ * specific to that test's activity (for example, custom views).
+ */
+public abstract class ClientSuggestionsCommonTestCase
+        extends AutoFillServiceTestCase.AutoActivityLaunch<ClientSuggestionsActivity> {
+
+    private static final String TAG = "ClientSuggestions";
+    protected ClientSuggestionsActivity mActivity;
+    protected ClientAutofillRequestCallback.Replier mClientReplier;
+
+    protected ClientSuggestionsCommonTestCase() {}
+
+    protected ClientSuggestionsCommonTestCase(UiBot inlineUiBot) {
+        super(inlineUiBot);
+    }
+
+    @Test
+    public void testAutoFillOneDataset() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mClientReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        sReplier.assertOnFillRequestNotCalled();
+        mClientReplier.assertReceivedRequest();
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAutoFillNoDatasets_fallbackDefaultService() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        mClientReplier.assertReceivedRequest();
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillNoDatasets_fallbackDefaultService() is enough")
+    public void testManualRequestAfterFallbackDefaultService() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        mClientReplier.assertReceivedRequest();
+
+        // The dataset shown.
+        mUiBot.assertDatasets("The Dude");
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "DUDE")
+                .setField(ID_PASSWORD, "SWEET")
+                .setPresentation("THE DUDE", isInlineMode())
+                .build());
+
+        // Trigger autofill.
+        mUiBot.waitForWindowChange(() -> mActivity.forceAutofillOnUsername());
+        sReplier.getNextFillRequest();
+        mClientReplier.assertNoUnhandledFillRequests();
+
+        mActivity.expectAutoFill("DUDE", "SWEET");
+
+        // Select the dataset.
+        mUiBot.selectDataset("THE DUDE");
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillNoDatasets_fallbackDefaultService() is enough")
+    public void testNewFieldAddedAfterFallbackDefaultService() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        mClientReplier.assertReceivedRequest();
+
+        // The dataset shown.
+        mUiBot.assertDatasets("The Dude");
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child, ID_EMPTY);
+        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+                "new view on the block");
+        child.addTextChangedListener(watcher);
+        sReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setField(ID_EMPTY, "new view on the block")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        mClientReplier.assertNoUnhandledFillRequests();
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+        mUiBot.waitForIdle();
+
+        // Check the results.
+        mActivity.assertAutoFilled();
+        watcher.assertAutoFilled();
+    }
+
+    @Test
+    public void testNoDatasetsAfterFallbackDefaultService() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        sReplier.addResponse(NO_RESPONSE);
+        mClientReplier.addResponse(NO_RESPONSE);
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+
+        mClientReplier.assertReceivedRequest();
+        sReplier.getNextFillRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testAutoFillNoDatasets() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        setEmptyClientResponse();
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+
+        mClientReplier.assertReceivedRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+    }
+
+    @Test
+    @AppModeFull(reason = "testAutoFillOneDataset() is enough")
+    public void testNewFieldAddedAfterFirstRequest() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        setEmptyClientResponse();
+
+        // Trigger autofill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mClientReplier.assertReceivedRequest();
+
+        // Make sure UI is not shown.
+        mUiBot.assertNoDatasetsEver();
+
+        // Try again, in a field that was added after the first request
+        final EditText child = new EditText(mActivity);
+        child.setId(R.id.empty);
+        mActivity.addChild(child, ID_EMPTY);
+        final OneTimeTextWatcher watcher = new OneTimeTextWatcher("child", child,
+                "new view on the block");
+        child.addTextChangedListener(watcher);
+        mClientReplier.addResponse(new CannedDataset.Builder()
+                .setField(ID_USERNAME, "dude")
+                .setField(ID_PASSWORD, "sweet")
+                .setField(ID_EMPTY, "new view on the block")
+                .setPresentation("The Dude", isInlineMode())
+                .build());
+
+        mActivity.syncRunOnUiThread(() -> child.requestFocus());
+
+        mClientReplier.assertReceivedRequest();
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select the dataset.
+        mUiBot.selectDataset("The Dude");
+
+        // Check the results.
+        // Check username and password fields
+        mActivity.assertAutoFilled();
+        // Check the new added field
+        watcher.assertAutoFilled();
+    }
+
+    @NonNull
+    @Override
+    protected AutofillActivityTestRule<ClientSuggestionsActivity> getActivityRule() {
+        return new AutofillActivityTestRule<ClientSuggestionsActivity>(
+                ClientSuggestionsActivity.class) {
+            @Override
+            protected void afterActivityLaunched() {
+                mActivity = getActivity();
+                mClientReplier = mActivity.getReplier();
+            }
+        };
+    }
+
+    private void setEmptyClientResponse() {
+        mClientReplier.addResponse(new CannedFillResponse.Builder()
+                .setExtras(new Bundle())
+                .build());
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
index 1880ed3..34e1eb1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/LoginActivityTest.java
@@ -2004,7 +2004,7 @@
                 mContext.unregisterReceiver(this);
                 latch.countDown();
             }
-        }, intentFilter);
+        }, intentFilter, Context.RECEIVER_NOT_EXPORTED);
 
         // Trigger the negative button.
         mUiBot.saveForAutofill(style, /* yesDoIt= */ false, SAVE_DATA_TYPE_PASSWORD);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
index 66ab8aa..3dc464d 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/dropdown/VirtualContainerActivityCompatModeTest.java
@@ -26,10 +26,8 @@
 import static android.autofillservice.cts.testcore.Helper.getContext;
 import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_NAME;
 import static android.autofillservice.cts.testcore.InstrumentedAutoFillServiceCompatMode.SERVICE_PACKAGE;
-import static android.provider.Settings.Global.AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
-
-import static com.android.compatibility.common.util.SettingsUtils.NAMESPACE_GLOBAL;
+import static android.view.autofill.AutofillManager.DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -45,13 +43,13 @@
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
 import android.service.autofill.SaveInfo;
 
-import com.android.compatibility.common.util.SettingsStateChangerRule;
-import com.android.compatibility.common.util.SettingsUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
 
 import org.junit.After;
-import org.junit.ClassRule;
+import org.junit.Before;
 import org.junit.Test;
 
 /**
@@ -60,18 +58,23 @@
  */
 public class VirtualContainerActivityCompatModeTest extends VirtualContainerActivityTest {
 
-    @ClassRule
-    public static final SettingsStateChangerRule sCompatModeChanger = new SettingsStateChangerRule(
-            sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
-            SERVICE_PACKAGE + "[my_url_bar]");
+    private final DeviceConfigStateHelper mAutofillDeviceConfig =
+            new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_AUTOFILL);
 
     public VirtualContainerActivityCompatModeTest() {
         super(true);
     }
 
+    @Before
+    public void setCompatMode() {
+        mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+                SERVICE_PACKAGE + "[my_url_bar]");
+    }
+
     @After
     public void resetCompatMode() {
         sContext.getApplicationContext().setAutofillOptions(null);
+        mAutofillDeviceConfig.restoreOriginalValues();
     }
 
     @Override
@@ -106,7 +109,7 @@
     @Presubmit
     @Test
     public void testMultipleUrlBars_firstDoesNotExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+        mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
                 SERVICE_PACKAGE + "[first_am_i,my_url_bar]");
 
         // Set service.
@@ -131,7 +134,7 @@
     @Test
     @AppModeFull(reason = "testMultipleUrlBars_firstDoesNotExist() is enough")
     public void testMultipleUrlBars_bothExist() throws Exception {
-        SettingsUtils.syncSet(sContext, NAMESPACE_GLOBAL, AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
+        mAutofillDeviceConfig.set(DEVICE_CONFIG_AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES,
                 SERVICE_PACKAGE + "[my_url_bar,my_url_bar2]");
 
         // Set service.
@@ -286,7 +289,7 @@
         // Fill in some stuff
         mActivity.mUsername.setText("foo");
         sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
-        SystemClock.sleep(300);
+        SystemClock.sleep(Timeouts.FILL_TIMEOUT.ms());
         focusToPasswordExpectNoWindowEvent();
         sReplier.getNextFillRequest();
         mActivity.mPassword.setText("bar");
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index 1ce3a8f..bd416b9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -40,6 +40,7 @@
 import android.autofillservice.cts.testcore.UiBot;
 import android.content.IntentSender;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.Presubmit;
 import android.view.inputmethod.InlineSuggestionsRequest;
 
@@ -177,6 +178,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 185876679)
     @AppModeFull(reason = "testDatasetAuthTwoFields() is enough")
     public void testDatasetAuthTwoFieldsUserCancelsFirstAttempt() throws Exception {
         datasetAuthTwoFields(/* cancelFirstAttempt */ true);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index f1e039d..7691b6b 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -374,6 +374,72 @@
     }
 
     @Test
+    public void testImeDisableServiceSuggestions_fallbackDropdownUi() throws Exception {
+        // Set service.
+        enableService();
+
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        // Disable inline suggestions for the default service.
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("ServiceSuggestions", false);
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Check that no inline requests are sent to the service.
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        assertThat(request.inlineRequest).isNull();
+
+        // Check dropdown UI shown.
+        getDropdownUiBot().assertDatasets("The Username");
+    }
+
+    @Test
+    public void testImeDisableInlineSuggestions_fallbackDropdownUi() throws Exception {
+        // Set service.
+        enableService();
+
+        final MockImeSession mockImeSession = sMockImeSessionRule.getMockImeSession();
+        assumeTrue("MockIME not available", mockImeSession != null);
+
+        // Disable inline suggestions for the default service.
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("InlineSuggestions", false);
+        mockImeSession.callSetInlineSuggestionsExtras(bundle);
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build());
+        sReplier.addResponse(builder.build());
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        // Check that no inline requests are sent to the service.
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        assertThat(request.inlineRequest).isNull();
+
+        // Check dropdown UI shown.
+        getDropdownUiBot().assertDatasets("The Username");
+    }
+
+    @Test
     public void testScrollSuggestionView() throws Exception {
         // Set service.
         enableService();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index d07f95b..75c28e2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -92,10 +92,8 @@
         mUiBot.assertNoDatasetsEver();
 
         // Change input
-        final SimpleSaveActivity.FillExpectation changeExpectation =
-                mActivity.expectInputTextChange("ID");
-        mActivity.syncRunOnUiThread(() -> mActivity.getInput().setText("ID"));
-        changeExpectation.assertTextChange();
+        mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */  null);
+
 
         // Trigger save UI.
         mUiBot.selectByRelativeId(ID_COMMIT);
@@ -147,10 +145,7 @@
         fillExpectation.assertAutoFilled();
 
         // Change input
-        final SimpleSaveActivity.FillExpectation changeExpectation =
-                mActivity.expectInputTextChange("ID");
-        mActivity.syncRunOnUiThread(() -> mActivity.getInput().setText("ID"));
-        changeExpectation.assertTextChange();
+        mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */  null);
 
         // Trigger save UI.
         mUiBot.selectByRelativeId(ID_COMMIT);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
index 8282fec..2d764bc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OnClickActionTest.java
@@ -117,9 +117,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "42", /* password= */  "108");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
-            mActivity.mPassword.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
index 1e38ae4..3231080 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/OptionalSaveActivityTest.java
@@ -752,10 +752,13 @@
         mActivity.assertAutoFilled();
 
         // Change required and optional field.
+        mActivity.expectTextChange(/* address1= */ null, /* address2= */
+                "Simpsons House", /* city= */ "Shelbyville", /* favColor= */ null);
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mAddress2.setText("Simpsons House");
             mActivity.mCity.setText("Shelbyville");
         });
+        mActivity.assertTextChange();
         // Trigger save...
         mActivity.save();
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
index f7a9030..0ee330c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/PreSimpleSaveActivityTest.java
@@ -69,7 +69,7 @@
     @Override
     protected void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
             throws Exception {
-        startActivity(false);
+        startActivity(/* remainOnRecents= */ false);
         // Set service.
         enableService();
 
@@ -85,10 +85,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange("108");
+        mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
@@ -128,7 +127,7 @@
     @Override
     protected void tapLinkThenTapBackThenStartOverTest(PostSaveLinkTappedAction action,
             boolean manualRequest) throws Exception {
-        startActivity(false);
+        startActivity(/* remainOnRecents= */ false);
         // Set service.
         enableService();
 
@@ -144,10 +143,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange("108");
+        mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
@@ -203,10 +201,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
+        newActivty.setTextAndWaitTextChange(/* input= */ "42", /* password= */  null);
+        newActivty.syncRunOnUiThread(() -> newActivty.mCommit.performClick());
+
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
@@ -221,7 +218,7 @@
     @Override
     protected void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
             throws Exception {
-        startActivity(false);
+        startActivity(/* remainOnRecents= */ false);
         // Set service.
         enableService();
 
@@ -237,10 +234,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange("108");
+        mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
@@ -276,7 +272,7 @@
     protected void tapLinkLaunchTrampolineActivityThenTapBackAndStartNewSessionTest()
             throws Exception {
         // Prepare activity.
-        startActivity(false);
+        startActivity(/* remainOnRecents= */ false);
         mActivity.mPreInput.getRootView()
                 .setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS);
 
@@ -295,10 +291,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange("108");
+        mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_PASSWORD);
 
         // Tap the link.
@@ -329,10 +324,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        newActivty.syncRunOnUiThread(() -> {
-            newActivty.mInput.setText("42");
-            newActivty.mCommit.performClick();
-        });
+        newActivty.setTextAndWaitTextChange(/* input= */ "42", /* password= */  null);
+        newActivty.syncRunOnUiThread(() -> newActivty.mCommit.performClick());
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
@@ -346,7 +339,7 @@
 
     @Override
     protected void tapLinkAfterUpdateAppliedTest(boolean updateLinkView) throws Exception {
-        startActivity(false);
+        startActivity(/* remainOnRecents= */ false);
         // Set service.
         enableService();
 
@@ -375,10 +368,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPreInput.setText("108");
-            mActivity.mSubmit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange("108");
+        mActivity.syncRunOnUiThread(() -> mActivity.mSubmit.performClick());
+
         // Make sure post-save activity is shown...
         mUiBot.assertShownByRelativeId(ID_INPUT);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
index 65d85bf..b06a187 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/saveui/SimpleSaveActivityTest.java
@@ -142,13 +142,12 @@
         sReplier.getNextFillRequest();
 
         // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        final FillExpectation autofillExpectation = mActivity.expectAutoFill("id", "pass");
         mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
+        autofillExpectation.assertAutoFilled();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -195,13 +194,12 @@
         Helper.assertEqualsToLargeString(hintsOnFill[2]);
 
         // Select dataset.
-        final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
+        final FillExpectation autofillExpectation = mActivity.expectAutoFill("id", "pass");
         mUiBot.selectDataset("YO");
-        autofillExpecation.assertAutoFilled();
+        autofillExpectation.assertAutoFilled();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -264,8 +262,11 @@
         assertWithMessage("wrong value for 'password'").that(visiblePassword).hasLength(4);
 
         // Trigger save...
+        final SimpleSaveActivity.FillExpectation changeExpectation =
+                mActivity.expectInputPasswordTextChange("ID", "PASS");
         input.setText("ID");
         password.setText("PASS");
+        changeExpectation.assertTextChange();
         mUiBot.assertShownByRelativeId(ID_COMMIT).click();
         mUiBot.updateForAutofill(true, SAVE_DATA_TYPE_GENERIC);
 
@@ -314,10 +315,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */  null);
+
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
 
         if (rotate) {
@@ -359,7 +359,7 @@
         sReplier.getNextFillRequest();
 
         // Set 1st field but don't commit session
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mUiBot.assertSaveNotShowing();
 
         // 2nd request
@@ -375,10 +375,9 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
+
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
         final UiObject2 saveUi = mUiBot.assertSaveShowing(null, SAVE_DATA_TYPE_USERNAME,
                 SAVE_DATA_TYPE_PASSWORD);
 
@@ -415,10 +414,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
-            mActivity.mCommit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
         mUiBot.assertSaveNotShowing();
 
         // 2nd fragment.
@@ -444,10 +441,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger delayed save.
-        mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
-            mActivity.mCommit.performClick();
-        });
+        mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
+        mActivity.syncRunOnUiThread(() -> mActivity.mCommit.performClick());
 
         // Save it...
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_USERNAME, SAVE_DATA_TYPE_PASSWORD);
@@ -485,8 +480,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
 
             // Disable autofill so it's not triggered again after WelcomeActivity finishes
@@ -520,8 +515,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
 
@@ -581,8 +576,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
 
@@ -604,8 +599,8 @@
                         .setPresentation(createPresentation("YO"))
                         .build())
                 .build());
+        mActivity.setTextAndWaitTextChange(/* input= */ "", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("");
             mActivity.getAutofillManager().requestAutofill(mActivity.mInput);
         });
         sReplier.getNextFillRequest();
@@ -637,8 +632,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         UiObject2 saveUi = mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -691,8 +686,8 @@
         mActivity.getAutofillManager().cancel();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
 
@@ -741,8 +736,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -834,8 +829,8 @@
         mUiBot.assertNoDatasetsEver();
 
         // Trigger save, but don't tap it.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         mUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
@@ -890,8 +885,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -958,8 +953,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1013,8 +1008,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "42", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("42");
             mActivity.mCommit.performClick();
         });
 
@@ -1045,8 +1040,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1120,9 +1115,8 @@
         mUiBot.selectDataset(picker2, "D2");
         autofillExpecation2.assertAutoFilled();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "ID", /* password= */ "PASS");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("ID");
-            mActivity.mPassword.setText("PASS");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = mUiBot.assertUpdateShowing(SAVE_DATA_TYPE_GENERIC);
@@ -1163,8 +1157,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi = assertSaveUiWithLinkIsShown(SAVE_DATA_TYPE_GENERIC);
@@ -1194,8 +1188,8 @@
                 () -> mActivity.getAutofillManager().requestAutofill(mActivity.mPassword));
         sReplier.getNextFillRequest();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ null, /* password= */ "42");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mPassword.setText("42");
             mActivity.mCommit.performClick();
         });
         mUiBot.saveForAutofill(true, SAVE_DATA_TYPE_PASSWORD);
@@ -1233,6 +1227,7 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        // TODO: handle wait text change for ntiTrimmerTextWatcher
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("id");
             mActivity.mPassword.setText("pass");
@@ -1275,9 +1270,8 @@
         mUiBot.assertNoDatasetsEver();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "#id#", /* password= */ "#pass#");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("#id#");
-            mActivity.mPassword.setText("#pass#");
             mActivity.mCommit.performClick();
         });
 
@@ -1323,6 +1317,7 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
 
+        // TODO: handle wait text change for ntiTrimmerTextWatcher
         mActivity.syncRunOnUiThread(() -> {
             mActivity.mInput.setText("id");
             mActivity.mPassword.setText("pass");
@@ -1361,9 +1356,8 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "#pass#");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("#pass#");
             mActivity.mCommit.performClick();
         });
 
@@ -1399,9 +1393,8 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "pass");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
             mActivity.mCommit.performClick();
         });
 
@@ -1439,9 +1432,8 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
 
+        mActivity.setTextAndWaitTextChange(/* input= */ "id", /* password= */ "pass");
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("id");
-            mActivity.mPassword.setText("pass");
             mActivity.mCommit.performClick();
         });
 
@@ -1609,8 +1601,8 @@
         if (condition == SetTextCondition.NORMAL) {
             sReplier.getNextFillRequest();
 
+            mActivity.setTextAndWaitTextChange(/* input= */ "100", /* password= */ "pass");
             mActivity.syncRunOnUiThread(() -> {
-                mActivity.mInput.setText("100");
                 mActivity.mCommit.performClick();
             });
 
@@ -1662,7 +1654,7 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
-        mActivity.syncRunOnUiThread(() -> mActivity.mInput.setText("108"));
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
 
         // Take a screenshot to make sure button doesn't disappear.
         final String commitBefore = mUiBot.assertShownByRelativeId(ID_COMMIT).getText();
@@ -1724,8 +1716,8 @@
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         final UiObject2 saveUi;
@@ -1862,8 +1854,8 @@
         sReplier.getNextFillRequest();
 
         // Trigger save.
+        mActivity.setTextAndWaitTextChange(/* input= */ "108", /* password= */ null);
         mActivity.syncRunOnUiThread(() -> {
-            mActivity.mInput.setText("108");
             mActivity.mCommit.performClick();
         });
         // Waits for the commit be processed
diff --git a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
index 94a88c4..009a9cb 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/servicebehavior/WebViewMultiScreenLoginActivityTest.java
@@ -24,7 +24,6 @@
 import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
 import static android.autofillservice.cts.testcore.Helper.assertTextAndValue;
 import static android.autofillservice.cts.testcore.Helper.findNodeByHtmlName;
-import static android.autofillservice.cts.testcore.Helper.getAutofillId;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_PASSWORD;
 import static android.service.autofill.SaveInfo.SAVE_DATA_TYPE_USERNAME;
 
@@ -103,7 +102,7 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME, HTML_NAME_USERNAME)
                 .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId usernameId = getAutofillId(nodeResolver, HTML_NAME_USERNAME);
+                    final AutofillId usernameId = nodeResolver.apply(HTML_NAME_USERNAME);
                     final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
                             .Builder(usernameId, MATCH_ALL, "$1").build();
                     builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
@@ -155,7 +154,7 @@
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .setRequiredSavableIds(SAVE_DATA_TYPE_PASSWORD, HTML_NAME_PASSWORD)
                 .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final AutofillId passwordId = nodeResolver.apply(HTML_NAME_PASSWORD);
                     final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
                             .Builder(passwordId, MATCH_ALL, "$1").build();
                     builder.setCustomDescription(newCustomDescriptionWithUsernameAndPassword()
@@ -219,7 +218,7 @@
                 .setIgnoreFields(HTML_NAME_USERNAME)
                 .setSaveInfoFlags(SaveInfo.FLAG_DELAY_SAVE)
                 .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    usernameId.set(getAutofillId(nodeResolver, HTML_NAME_USERNAME));
+                    usernameId.set(nodeResolver.apply(HTML_NAME_USERNAME));
 
                 })
                 .build());
@@ -258,7 +257,7 @@
                 .setRequiredSavableIds(SAVE_DATA_TYPE_USERNAME | SAVE_DATA_TYPE_PASSWORD,
                         HTML_NAME_PASSWORD)
                 .setSaveInfoDecorator((builder, nodeResolver) -> {
-                    final AutofillId passwordId = getAutofillId(nodeResolver, HTML_NAME_PASSWORD);
+                    final AutofillId passwordId = nodeResolver.apply(HTML_NAME_PASSWORD);
                     final CharSequenceTransformation usernameTrans = new CharSequenceTransformation
                             .Builder(usernameId.get(), MATCH_ALL, "$1").build();
                     final CharSequenceTransformation passwordTrans = new CharSequenceTransformation
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
index 99ee293..3ec1f12 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/CannedFillResponse.java
@@ -171,12 +171,28 @@
      */
     public FillResponse asFillResponse(@Nullable List<FillContext> contexts,
             @NonNull Function<String, ViewNode> nodeResolver) {
+        return asFillResponseWithAutofillId(contexts, (id)-> {
+            ViewNode node = nodeResolver.apply(id);
+            if (node == null) {
+                throw new AssertionError("No node with resource id " + id);
+            }
+            return node.getAutofillId();
+        });
+    }
+
+    /**
+     * Creates a new response, replacing the dataset field ids by the real ids from the assist
+     * structure.
+     */
+    public FillResponse asFillResponseWithAutofillId(@Nullable List<FillContext> contexts,
+            @NonNull Function<String, AutofillId> autofillIdResolver) {
         final FillResponse.Builder builder = new FillResponse.Builder()
                 .setFlags(mFillResponseFlags);
         if (mDatasets != null) {
             for (CannedDataset cannedDataset : mDatasets) {
-                final Dataset dataset = cannedDataset.asDataset(nodeResolver);
-                assertWithMessage("Cannot create datase").that(dataset).isNotNull();
+                final Dataset dataset =
+                        cannedDataset.asDatasetWithAutofillIdResolver(autofillIdResolver);
+                assertWithMessage("Cannot create dataset").that(dataset).isNotNull();
                 builder.addDataset(dataset);
             }
         }
@@ -189,13 +205,14 @@
                 saveInfoBuilder = mRequiredSavableIds == null || mRequiredSavableIds.length == 0
                         ? new SaveInfo.Builder(mSaveType)
                             : new SaveInfo.Builder(mSaveType,
-                                    getAutofillIds(nodeResolver, mRequiredSavableIds));
+                                    getAutofillIds(autofillIdResolver, mRequiredSavableIds));
             }
 
             saveInfoBuilder.setFlags(mSaveInfoFlags);
 
             if (mOptionalSavableIds != null) {
-                saveInfoBuilder.setOptionalIds(getAutofillIds(nodeResolver, mOptionalSavableIds));
+                saveInfoBuilder.setOptionalIds(
+                        getAutofillIds(autofillIdResolver, mOptionalSavableIds));
             }
             if (mSaveDescription != null) {
                 saveInfoBuilder.setDescription(mSaveDescription);
@@ -217,7 +234,7 @@
         if (saveInfoBuilder != null) {
             // TODO: merge decorator and visitor
             if (mSaveInfoDecorator != null) {
-                mSaveInfoDecorator.decorate(saveInfoBuilder, nodeResolver);
+                mSaveInfoDecorator.decorate(saveInfoBuilder, autofillIdResolver);
             }
             if (mSaveInfoVisitor != null) {
                 Log.d(TAG, "Visiting saveInfo " + saveInfoBuilder);
@@ -228,10 +245,10 @@
             builder.setSaveInfo(saveInfo);
         }
         if (mIgnoredIds != null) {
-            builder.setIgnoredIds(getAutofillIds(nodeResolver, mIgnoredIds));
+            builder.setIgnoredIds(getAutofillIds(autofillIdResolver, mIgnoredIds));
         }
         if (mAuthenticationIds != null) {
-            builder.setAuthentication(getAutofillIds(nodeResolver, mAuthenticationIds),
+            builder.setAuthentication(getAutofillIds(autofillIdResolver, mAuthenticationIds),
                     mAuthentication, mPresentation, mInlinePresentation);
         }
         if (mDisableDuration > 0) {
@@ -246,7 +263,7 @@
             builder.setFieldClassificationIds(fieldIds);
         } else if (mFieldClassificationIds != null) {
             builder.setFieldClassificationIds(
-                    getAutofillIds(nodeResolver, mFieldClassificationIds));
+                    getAutofillIds(autofillIdResolver, mFieldClassificationIds));
         }
         if (mExtras != null) {
             builder.setClientState(mExtras);
@@ -610,7 +627,21 @@
         /**
          * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
          */
-        public Dataset asDataset(Function<String, ViewNode> nodeResolver) {
+        public Dataset asDatasetWithNodeResolver(Function<String, ViewNode> nodeResolver) {
+            return asDatasetWithAutofillIdResolver((id) -> {
+                ViewNode node = nodeResolver.apply(id);
+                if (node == null) {
+                    throw new AssertionError("No node with resource id " + id);
+                }
+                return node.getAutofillId();
+            });
+        }
+
+        /**
+         * Creates a new dataset, replacing the field ids by the real ids from the assist structure.
+         */
+        public Dataset asDatasetWithAutofillIdResolver(
+                Function<String, AutofillId> autofillIdResolver) {
             final Dataset.Builder builder = mPresentation != null
                     ? new Dataset.Builder(mPresentation)
                     : new Dataset.Builder();
@@ -625,11 +656,11 @@
             if (mFieldValues != null) {
                 for (Map.Entry<String, AutofillValue> entry : mFieldValues.entrySet()) {
                     final String id = entry.getKey();
-                    final ViewNode node = nodeResolver.apply(id);
-                    if (node == null) {
+
+                    final AutofillId autofillId = autofillIdResolver.apply(id);
+                    if (autofillId == null) {
                         throw new AssertionError("No node with resource id " + id);
                     }
-                    final AutofillId autofillId = node.getAutofillId();
                     final AutofillValue value = entry.getValue();
                     final RemoteViews presentation = mFieldPresentations.get(id);
                     final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(id);
@@ -950,6 +981,6 @@
     }
 
     public interface SaveInfoDecorator {
-        void decorate(SaveInfo.Builder builder, Function<String, ViewNode> nodeResolver);
+        void decorate(SaveInfo.Builder builder, Function<String, AutofillId> nodeResolver);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java
new file mode 100644
index 0000000..b970785
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/ClientAutofillRequestCallback.java
@@ -0,0 +1,379 @@
+/*
+ * 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.autofillservice.cts.testcore;
+
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.FAILURE;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.NULL;
+import static android.autofillservice.cts.testcore.CannedFillResponse.ResponseType.TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.CONNECTION_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.FILL_TIMEOUT;
+import static android.autofillservice.cts.testcore.Timeouts.RESPONSE_DELAY_MS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.service.autofill.AutofillService;
+import android.service.autofill.Dataset;
+import android.service.autofill.FillCallback;
+import android.service.autofill.FillContext;
+import android.service.autofill.FillResponse;
+import android.service.autofill.SaveCallback;
+import android.util.Log;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillRequestCallback;
+import android.view.inputmethod.InlineSuggestionsRequest;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.RetryableException;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+
+/**
+ * Implements an {@link AutofillRequestCallback} for testing client suggestions behavior.
+ */
+public class ClientAutofillRequestCallback implements AutofillRequestCallback {
+    private static final String TAG = "ClientAutofillRequestCallback";
+    private final Handler mHandler;
+    private final @NonNull Function<String, AutofillId> mIdResolver;
+    private final Replier mReplier;
+
+    public ClientAutofillRequestCallback(@NonNull  Handler handler,
+            @NonNull Function<String, AutofillId> idResolver) {
+        mHandler = handler;
+        mIdResolver = idResolver;
+        mReplier = new Replier(mIdResolver);
+    }
+
+    @Override
+    public void onFillRequest(InlineSuggestionsRequest inlineSuggestionsRequest,
+            CancellationSignal cancellationSignal, FillCallback callback) {
+
+        if (!TestNameUtils.isRunningTest()) {
+            Log.e(TAG, "onFillRequest(client) called after tests finished");
+            return;
+        }
+
+        mHandler.post(
+                () -> mReplier.onFillRequest(
+                        cancellationSignal, callback, inlineSuggestionsRequest));
+    }
+
+    public Replier getReplier() {
+        return mReplier;
+    }
+
+    /**
+     * POJO representation of the contents of a
+     * {@link ClientAutofillRequestCallback#onFillRequest(InlineSuggestionsRequest,
+     * CancellationSignal, FillCallback)} that can be asserted at the end of a test case.
+     */
+    public static final class FillRequest {
+
+        public final CancellationSignal cancellationSignal;
+        public final FillCallback callback;
+        public final InlineSuggestionsRequest inlineRequest;
+
+        private FillRequest(CancellationSignal cancellationSignal, FillCallback callback,
+                InlineSuggestionsRequest inlineRequest) {
+            this.cancellationSignal = cancellationSignal;
+            this.callback = callback;
+            this.inlineRequest = inlineRequest;
+        }
+
+        @Override
+        public String toString() {
+            return "FillRequest[has inlineRequest=" + (inlineRequest != null) + "]";
+        }
+    }
+
+    /**
+     * Object used to answer a
+     * {@link AutofillRequestCallback#onFillRequest(InlineSuggestionsRequest, CancellationSignal,
+     * FillCallback)}
+     * on behalf of a unit test method.
+     */
+    public static final class Replier {
+        // TODO: refactor with InstrumentedAutoFillService$Replier
+
+        private final BlockingQueue<CannedFillResponse> mResponses = new LinkedBlockingQueue<>();
+        private final BlockingQueue<FillRequest> mFillRequests =
+                new LinkedBlockingQueue<>();
+
+        private List<Throwable> mExceptions;
+        private IntentSender mOnSaveIntentSender;
+        private String mAcceptedPackageName;
+
+        private Handler mHandler;
+
+        private boolean mReportUnhandledFillRequest = true;
+        private boolean mReportUnhandledSaveRequest = true;
+        private final @NonNull Function<String, AutofillId> mIdResolver;
+
+        private Replier(@NonNull Function<String, AutofillId> idResolver) {
+            mIdResolver = idResolver;
+        }
+
+        public void acceptRequestsFromPackage(String packageName) {
+            mAcceptedPackageName = packageName;
+        }
+
+        /**
+         * Gets the exceptions thrown asynchronously, if any.
+         */
+        @Nullable
+        public List<Throwable> getExceptions() {
+            return mExceptions;
+        }
+
+        private void addException(@Nullable Throwable e) {
+            if (e == null) return;
+
+            if (mExceptions == null) {
+                mExceptions = new ArrayList<>();
+            }
+            mExceptions.add(e);
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest} as {@link FillResponse} with
+         * just one {@link Dataset}.
+         */
+        public Replier addResponse(
+                CannedFillResponse.CannedDataset dataset) {
+            return addResponse(new CannedFillResponse.Builder()
+                    .addDataset(dataset)
+                    .build());
+        }
+
+        /**
+         * Sets the expectation for the next {@code onFillRequest}.
+         */
+        public Replier addResponse(CannedFillResponse response) {
+            if (response == null) {
+                throw new IllegalArgumentException("Cannot be null - use NO_RESPONSE instead");
+            }
+            mResponses.add(response);
+            return this;
+        }
+
+        /**
+         * Sets the {@link IntentSender} that is passed to
+         * {@link SaveCallback#onSuccess(IntentSender)}.
+         */
+        public Replier setOnSave(IntentSender intentSender) {
+            mOnSaveIntentSender = intentSender;
+            return this;
+        }
+
+        /**
+         * Gets the next fill request, in the order received.
+         */
+        public FillRequest getNextFillRequest() {
+            FillRequest request;
+            try {
+                request = mFillRequests.poll(FILL_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                throw new IllegalStateException("Interrupted", e);
+            }
+            if (request == null) {
+                throw new RetryableException(FILL_TIMEOUT, "onFillRequest() not called");
+            }
+            return request;
+        }
+
+        /**
+         * Assets the client had received fill request.
+         */
+        public void assertReceivedRequest() {
+            getNextFillRequest();
+        }
+
+        /**
+         * Asserts that {@link AutofillRequestCallback#onFillRequest(InlineSuggestionsRequest,
+         * CancellationSignal, FillCallback)} was not called.
+         *
+         * <p>Should only be called in cases where it's not expected to be called, as it will
+         * sleep for a few ms.
+         */
+        public void assertOnFillRequestNotCalled() {
+            SystemClock.sleep(FILL_TIMEOUT.getMaxValue());
+            assertThat(mFillRequests).isEmpty();
+        }
+
+        /**
+         * Asserts all {@link AutofillService#onFillRequest(
+         * android.service.autofill.FillRequest,  CancellationSignal, FillCallback) fill requests}
+         * received by the service were properly {@link #getNextFillRequest() handled} by the test
+         * case.
+         */
+        public void assertNoUnhandledFillRequests() {
+            if (mFillRequests.isEmpty()) return; // Good job, test case!
+
+            if (!mReportUnhandledFillRequest) {
+                // Just log, so it's not thrown again on @After if already thrown on main body
+                Log.d(TAG, "assertNoUnhandledFillRequests(): already reported, "
+                        + "but logging just in case: " + mFillRequests);
+                return;
+            }
+
+            mReportUnhandledFillRequest = false;
+            throw new AssertionError(mFillRequests.size()
+                    + " unhandled fill requests: " + mFillRequests);
+        }
+
+        /**
+         * Gets the current number of unhandled requests.
+         */
+        public int getNumberUnhandledFillRequests() {
+            return mFillRequests.size();
+        }
+
+        public void setHandler(Handler handler) {
+            mHandler = handler;
+        }
+
+        /**
+         * Resets its internal state.
+         */
+        public void reset() {
+            mResponses.clear();
+            mFillRequests.clear();
+            mExceptions = null;
+            mOnSaveIntentSender = null;
+            mAcceptedPackageName = null;
+            mReportUnhandledFillRequest = true;
+            mReportUnhandledSaveRequest = true;
+        }
+
+        public void onFillRequest(CancellationSignal cancellationSignal, FillCallback callback,
+                InlineSuggestionsRequest inlineSuggestionsRequest) {
+            try {
+                CannedFillResponse response = null;
+                try {
+                    response = mResponses.poll(CONNECTION_TIMEOUT.ms(), TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.w(TAG, "Interrupted getting CannedResponse: " + e);
+                    Thread.currentThread().interrupt();
+                    addException(e);
+                    return;
+                }
+                if (response == null) {
+                    Log.d(TAG, "response is null");
+                    return;
+                }
+                if (response.getResponseType() == NULL) {
+                    Log.d(TAG, "onFillRequest(): replying with null");
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                if (response.getResponseType() == TIMEOUT) {
+                    Log.d(TAG, "onFillRequest(): not replying at all");
+                    return;
+                }
+
+                if (response.getResponseType() == FAILURE) {
+                    Log.d(TAG, "onFillRequest(): replying with failure");
+                    callback.onFailure("D'OH!");
+                    return;
+                }
+
+                if (response.getResponseType() == CannedFillResponse.ResponseType.NO_MORE) {
+                    Log.w(TAG, "onFillRequest(): replying with null when not expecting more");
+                    addException(new IllegalStateException("got unexpected request"));
+                    callback.onSuccess(null);
+                    return;
+                }
+
+                final String failureMessage = response.getFailureMessage();
+                if (failureMessage != null) {
+                    Log.v(TAG, "onFillRequest(): failureMessage = " + failureMessage);
+                    callback.onFailure(failureMessage);
+                    return;
+                }
+
+                final FillResponse fillResponse;
+                fillResponse = response.asFillResponseWithAutofillId(null, mIdResolver);
+
+                if (response.getResponseType() == CannedFillResponse.ResponseType.DELAY) {
+                    mHandler.postDelayed(() -> {
+                        Log.v(TAG,
+                                "onFillRequest(): fillResponse = " + fillResponse);
+                        callback.onSuccess(fillResponse);
+                        // Add a fill request to let test case know response was sent.
+                        Helper.offer(
+                                mFillRequests,
+                                new FillRequest(cancellationSignal, callback,
+                                        inlineSuggestionsRequest),
+                                CONNECTION_TIMEOUT.ms());
+                    }, RESPONSE_DELAY_MS);
+                } else {
+                    Log.v(TAG, "onFillRequest(): fillResponse = " + fillResponse);
+                    callback.onSuccess(fillResponse);
+                }
+            } catch (Throwable t) {
+                Log.d(TAG, "onFillRequest(): catch a Throwable: " + t);
+                addException(t);
+            } finally {
+                Helper.offer(
+                        mFillRequests,
+                        new FillRequest(cancellationSignal, callback,
+                                inlineSuggestionsRequest),
+                        CONNECTION_TIMEOUT.ms());
+            }
+        }
+
+        private void onSaveRequest(List<FillContext> contexts, Bundle data, SaveCallback callback,
+                List<String> datasetIds) {
+            Log.d(TAG, "onSaveRequest(): sender=" + mOnSaveIntentSender);
+
+            try {
+                if (mOnSaveIntentSender != null) {
+                    callback.onSuccess(mOnSaveIntentSender);
+                } else {
+                    callback.onSuccess();
+                }
+            } finally {
+                //TODO
+            }
+        }
+
+        private void dump(PrintWriter pw) {
+            pw.print("mResponses: "); pw.println(mResponses);
+            pw.print("mFillRequests: "); pw.println(mFillRequests);
+            pw.print("mExceptions: "); pw.println(mExceptions);
+            pw.print("mOnSaveIntentSender: "); pw.println(mOnSaveIntentSender);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mAcceptedPackageName: "); pw.println(mAcceptedPackageName);
+            pw.print("mReportUnhandledFillRequest: "); pw.println(mReportUnhandledSaveRequest);
+        }
+    }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 21befa5..6149ec9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -814,39 +814,19 @@
      * Creates an array of {@link AutofillId} mapped from the {@code structure} nodes with the given
      * {@code resourceIds}.
      */
-    public static AutofillId[] getAutofillIds(Function<String, ViewNode> nodeResolver,
+    public static AutofillId[] getAutofillIds(Function<String, AutofillId> autofillIdResolver,
             String[] resourceIds) {
         if (resourceIds == null) return null;
 
         final AutofillId[] requiredIds = new AutofillId[resourceIds.length];
         for (int i = 0; i < resourceIds.length; i++) {
             final String resourceId = resourceIds[i];
-            final ViewNode node = nodeResolver.apply(resourceId);
-            if (node == null) {
-                throw new AssertionError("No node with resourceId " + resourceId);
-            }
-            requiredIds[i] = node.getAutofillId();
-
+            requiredIds[i] = autofillIdResolver.apply(resourceId);
         }
         return requiredIds;
     }
 
     /**
-     * Get an {@link AutofillId} mapped from the {@code structure} node with the given
-     * {@code resourceId}.
-     */
-    public static AutofillId getAutofillId(Function<String, ViewNode> nodeResolver,
-            String resourceId) {
-        if (resourceId == null) return null;
-
-        final ViewNode node = nodeResolver.apply(resourceId);
-        if (node == null) {
-            throw new AssertionError("No node with resourceId " + resourceId);
-        }
-        return node.getAutofillId();
-    }
-
-    /**
      * Prevents the screen to rotate by itself
      */
     public static void disableAutoRotation(UiBot uiBot) throws Exception {
diff --git a/tests/backup/AndroidTest.xml b/tests/backup/AndroidTest.xml
index ec0ade5..0e84662 100644
--- a/tests/backup/AndroidTest.xml
+++ b/tests/backup/AndroidTest.xml
@@ -33,11 +33,14 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
         <option name="test-file-name" value="CtsFullBackupApp.apk" />
         <option name="test-file-name" value="CtsKeyValueBackupApp.apk" />
         <option name="test-file-name" value="CtsBackupTestCases.apk" />
         <option name="test-file-name" value="CtsPermissionBackupApp.apk" />
         <option name="test-file-name" value="CtsPermissionBackupApp22.apk" />
+        <option name="test-file-name" value="CtsAppLocalesBackupApp1.apk" />
+        <option name="test-file-name" value="CtsAppLocalesBackupApp2.apk" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/backup" />
@@ -48,6 +51,8 @@
         <option name="cleanup" value="true" />
         <option name="push" value="CtsPermissionBackupApp.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp.apk" />
         <option name="push" value="CtsPermissionBackupApp22.apk->/data/local/tmp/cts/backup/CtsPermissionBackupApp22.apk" />
+        <option name="push" value="CtsAppLocalesBackupApp1.apk->/data/local/tmp/cts/backup/CtsAppLocalesBackupApp1.apk" />
+        <option name="push" value="CtsAppLocalesBackupApp2.apk->/data/local/tmp/cts/backup/CtsAppLocalesBackupApp2.apk" />
     </target_preparer>
     <target_preparer class="android.cts.backup.BackupPreparer">
         <option name="enable-backup-if-needed" value="true" />
diff --git a/tests/backup/app/Android.bp b/tests/backup/app/Android.bp
index da52a3e..341b841 100644
--- a/tests/backup/app/Android.bp
+++ b/tests/backup/app/Android.bp
@@ -36,7 +36,7 @@
         "mts",
     ],
     platform_apis: true,
-    manifest: "fullbackup/AndroidManifest.xml"
+    manifest: "fullbackup/AndroidManifest.xml",
 }
 
 android_test_helper_app {
@@ -60,7 +60,7 @@
         "mts",
     ],
     platform_apis: true,
-    manifest: "keyvalue/AndroidManifest.xml"
+    manifest: "keyvalue/AndroidManifest.xml",
 }
 
 android_test_helper_app {
@@ -84,7 +84,7 @@
         "mts",
     ],
     platform_apis: true,
-    manifest: "permission/AndroidManifest.xml"
+    manifest: "permission/AndroidManifest.xml",
 }
 
 android_test_helper_app {
@@ -106,5 +106,53 @@
         "mts",
     ],
     platform_apis: true,
-    manifest: "permission22/AndroidManifest.xml"
+    manifest: "permission22/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsAppLocalesBackupApp1",
+    defaults: [
+        "cts_support_defaults",
+        "mts-target-sdk-version-current",
+    ],
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    platform_apis: true,
+    manifest: "AppLocalesBackupApp1/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+    name: "CtsAppLocalesBackupApp2",
+    defaults: [
+        "cts_support_defaults",
+        "mts-target-sdk-version-current",
+    ],
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts",
+    ],
+    platform_apis: true,
+    manifest: "AppLocalesBackupApp2/AndroidManifest.xml",
 }
diff --git a/tests/backup/app/AppLocalesBackupApp1/AndroidManifest.xml b/tests/backup/app/AppLocalesBackupApp1/AndroidManifest.xml
new file mode 100644
index 0000000..719714c
--- /dev/null
+++ b/tests/backup/app/AppLocalesBackupApp1/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="android.cts.backup.applocalesbackupapp1">
+    <application
+        android:label="AppLocalesBackupApp1">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/backup/app/AppLocalesBackupApp2/AndroidManifest.xml b/tests/backup/app/AppLocalesBackupApp2/AndroidManifest.xml
new file mode 100644
index 0000000..7bb7592
--- /dev/null
+++ b/tests/backup/app/AppLocalesBackupApp2/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="android.cts.backup.applocalesbackupapp2">
+    <application
+        android:label="AppLocalesBackupApp2">
+        <uses-library android:name="android.test.runner" />
+    </application>
+</manifest>
diff --git a/tests/backup/app/fullbackup/AndroidManifest.xml b/tests/backup/app/fullbackup/AndroidManifest.xml
index 7f639f9..8a5e5f6 100644
--- a/tests/backup/app/fullbackup/AndroidManifest.xml
+++ b/tests/backup/app/fullbackup/AndroidManifest.xml
@@ -21,7 +21,8 @@
     <application android:allowBackup="true"
          android:backupAgent="FullBackupBackupAgent"
          android:label="Android Backup CTS App"
-         android:fullBackupOnly="true">
+         android:fullBackupOnly="true"
+         android:testOnly="true">
         <uses-library android:name="android.test.runner"/>
 
 
diff --git a/tests/backup/app/keyvalue/AndroidManifest.xml b/tests/backup/app/keyvalue/AndroidManifest.xml
index c36d70c..937d344 100644
--- a/tests/backup/app/keyvalue/AndroidManifest.xml
+++ b/tests/backup/app/keyvalue/AndroidManifest.xml
@@ -20,7 +20,8 @@
 
     <application android:allowBackup="true"
          android:backupAgent="android.backup.app.KeyValueBackupAgent"
-         android:label="Android Key Value Backup CTS App">
+         android:label="Android Key Value Backup CTS App"
+         android:testOnly="true">
         <uses-library android:name="android.test.runner"/>
 
         <activity android:name="android.backup.app.MainActivity"
diff --git a/tests/backup/src/android/backup/cts/AgentBindingTest.java b/tests/backup/src/android/backup/cts/AgentBindingTest.java
index 1a9fe01..e705e49 100644
--- a/tests/backup/src/android/backup/cts/AgentBindingTest.java
+++ b/tests/backup/src/android/backup/cts/AgentBindingTest.java
@@ -16,25 +16,57 @@
 
 package android.backup.cts;
 
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static org.testng.Assert.expectThrows;
 
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.IBinder;
 import android.platform.test.annotations.AppModeFull;
 
+import java.io.IOException;
 import java.lang.reflect.Method;
+import java.util.Scanner;
 
 @AppModeFull
 public class AgentBindingTest extends BaseBackupCtsTest {
+    private static final String FULL_BACKUP_PACKAGE_NAME = "android.backup.app";
+    private static final String KEY_VALUE_BACKUP_PACKAGE_NAME = "android.backup.kvapp";
+    private static final ComponentName FULL_BACKUP_AGENT_NAME = ComponentName.createRelative(
+            FULL_BACKUP_PACKAGE_NAME, ".FullBackupBackupAgent");
+    private static final ComponentName KEY_VALUE_BACKUP_AGENT_NAME = ComponentName.createRelative(
+            KEY_VALUE_BACKUP_PACKAGE_NAME, "android.backup.app.KeyValueBackupAgent");
+    private static final int LOCAL_TRANSPORT_CONFORMING_FILE_SIZE = 5 * 1024;
+
     private Context mContext;
 
+    // Save the states before running tests. Restore them after tests finished.
+    private int mFullBackupAgentEnabledState;
+    private int mKeyValueBackupAgentEnabledState;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mContext = getInstrumentation().getTargetContext();
+        mFullBackupAgentEnabledState = mContext.getPackageManager().getComponentEnabledSetting(
+                FULL_BACKUP_AGENT_NAME);
+        mKeyValueBackupAgentEnabledState = mContext.getPackageManager().getComponentEnabledSetting(
+                KEY_VALUE_BACKUP_AGENT_NAME);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        setComponentEnabledSetting(FULL_BACKUP_AGENT_NAME, mFullBackupAgentEnabledState);
+        setComponentEnabledSetting(KEY_VALUE_BACKUP_AGENT_NAME, mKeyValueBackupAgentEnabledState);
     }
 
     public void testUnbindBackupAgent_isNotCallableFromCts() throws Exception {
@@ -51,6 +83,76 @@
         expectThrows(Exception.class, () -> bindBackupAgent(mContext.getPackageName(), 0, 0));
     }
 
+    public void testFullBackupAgentComponentDisabled() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setComponentEnabledSetting(FULL_BACKUP_AGENT_NAME, COMPONENT_ENABLED_STATE_DISABLED);
+        // Make sure there's something to backup
+        createTestFileOfSize(FULL_BACKUP_PACKAGE_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
+
+        runBackupAndAssertAgentError(FULL_BACKUP_PACKAGE_NAME);
+    }
+
+    public void testKeyValueBackupAgentComponentDisabled() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setComponentEnabledSetting(KEY_VALUE_BACKUP_AGENT_NAME, COMPONENT_ENABLED_STATE_DISABLED);
+        // Make sure there's something to backup
+        createTestFileOfSize(KEY_VALUE_BACKUP_PACKAGE_NAME, LOCAL_TRANSPORT_CONFORMING_FILE_SIZE);
+
+        runBackupAndAssertAgentError(KEY_VALUE_BACKUP_PACKAGE_NAME);
+    }
+
+    private void setComponentEnabledSetting(ComponentName componentName, int enabledState)
+            throws IOException {
+        final StringBuilder cmd = new StringBuilder("pm ");
+        switch (enabledState) {
+            case COMPONENT_ENABLED_STATE_DEFAULT:
+                cmd.append("default-state ");
+                break;
+            case COMPONENT_ENABLED_STATE_ENABLED:
+                cmd.append("enable ");
+                break;
+            case COMPONENT_ENABLED_STATE_DISABLED:
+                cmd.append("disable ");
+                break;
+            case COMPONENT_ENABLED_STATE_DISABLED_USER:
+                cmd.append("disable-user ");
+                break;
+            case COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                cmd.append("disable-until-used ");
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid enabled state:" + enabledState);
+        }
+        cmd.append("--user cur ").append(componentName.flattenToString());
+        getBackupUtils().executeShellCommandSync(cmd.toString());
+    }
+
+    /**
+     * Parses the output of "bmgr backupnow" command and checks an agent error during
+     * backup / restore.
+     */
+    private void runBackupAndAssertAgentError(String packageName) throws IOException {
+        Scanner in = new Scanner(getBackupUtils().getBackupNowOutput(packageName));
+        boolean found = false;
+        while (in.hasNextLine()) {
+            String line = in.nextLine();
+
+            if (line.contains(packageName)) {
+                String result = line.split(":")[1].trim();
+                if ("Agent error".equals(result)) {
+                    found = true;
+                    break;
+                }
+            }
+        }
+        in.close();
+        assertTrue("Didn't find \'Agent error\' in the output", found);
+    }
+
     private static void unbindBackupAgent(ApplicationInfo applicationInfo) throws Exception {
         callActivityManagerMethod(
                 "unbindBackupAgent",
diff --git a/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java b/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java
new file mode 100644
index 0000000..defdb7c
--- /dev/null
+++ b/tests/backup/src/android/backup/cts/AppLocalesBackupTest.java
@@ -0,0 +1,300 @@
+/*
+ * 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.backup.cts;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import android.Manifest;
+import android.app.LocaleManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.LocaleList;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@AppModeFull
+public class AppLocalesBackupTest extends BaseBackupCtsTest {
+    private static final String APK_PATH = "/data/local/tmp/cts/backup/";
+    private static final String TEST_APP_APK_1 = APK_PATH + "CtsAppLocalesBackupApp1.apk";
+    private static final String TEST_APP_PACKAGE_1 =
+            "android.cts.backup.applocalesbackupapp1";
+
+    private static final String TEST_APP_APK_2 = APK_PATH + "CtsAppLocalesBackupApp2.apk";
+    private static final String TEST_APP_PACKAGE_2 =
+            "android.cts.backup.applocalesbackupapp2";
+    private static final String SYSTEM_PACKAGE = "android";
+
+    private static final LocaleList DEFAULT_LOCALES_1 = LocaleList.forLanguageTags("hi-IN,de-DE");
+    private static final LocaleList DEFAULT_LOCALES_2 = LocaleList.forLanguageTags("fr-CA");
+    private static final LocaleList EMPTY_LOCALES = LocaleList.getEmptyLocaleList();
+
+    // An identifier for the backup dataset. Since we're using localtransport, it's set to "1".
+    private static final String RESTORE_TOKEN = "1";
+
+    private Context mContext;
+    private LocaleManager mLocaleManager;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mContext = InstrumentationRegistry.getTargetContext();
+        mLocaleManager = mContext.getSystemService(LocaleManager.class);
+
+        install(TEST_APP_APK_1);
+        install(TEST_APP_APK_2);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        uninstall(TEST_APP_PACKAGE_1);
+        uninstall(TEST_APP_PACKAGE_2);
+    }
+
+    /**
+     * Tests the scenario where all apps are installed on the device when restore is triggered.
+     *
+     * <p>In this case, all the apps should have their locales restored as soon as the restore
+     * operation finishes. The only condition is that the apps should not have the locales set
+     * already before restore.
+     */
+    public void testBackupRestore_allAppsInstalledNoAppLocalesSet_restoresImmediately()
+            throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setAndBackupDefaultAppLocales();
+
+        resetAppLocales();
+
+        getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+        assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+        assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+    }
+
+    /**
+     * Tests the scenario where the user sets the app-locales before the restore could be applied.
+     *
+     * <p>The locales from the backup data should be ignored in this case.
+     */
+    public void testBackupRestore_localeAlreadySet_doesNotRestore() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setAndBackupDefaultAppLocales();
+
+        LocaleList newLocales = LocaleList.forLanguageTags("zh,hi");
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, newLocales);
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+
+        getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+        // Should restore only for app_2.
+        assertLocalesForApp(TEST_APP_PACKAGE_1, newLocales);
+        assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+    }
+
+    /**
+     * Tests the scenario when some apps are installed after the restore finishes.
+     *
+     * <p>More specifically, this tests the lazy restore where the locales are fetched and
+     * restored from the stage file if the app is installed within a certain amount of time after
+     * the initial restore.
+     */
+    public void testBackupRestore_appInstalledAfterRestore_doesLazyRestore() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setAndBackupDefaultAppLocales();
+
+        resetAppLocales();
+
+        uninstall(TEST_APP_PACKAGE_2);
+
+        getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+        // Locales for App1 should be restored immediately since that's present already.
+        assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+
+        // This is to ensure there are no lingering broadcasts (could be from the setUp method
+        // where we are calling setApplicationLocales).
+        AmUtils.waitForBroadcastIdle();
+
+        BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+                new BlockingBroadcastReceiver();
+        mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+        // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent,
+        // so that we receive it.
+        runWithShellPermissionIdentity(() -> {
+            // Installation will trigger lazy restore, which internally calls setApplicationLocales
+            // which sends out the ACTION_APPLICATION_LOCALE_CHANGED broadcast.
+            install(TEST_APP_APK_2);
+            appSpecificLocaleBroadcastReceiver.await();
+        }, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+        appSpecificLocaleBroadcastReceiver.assertOneBroadcastReceived();
+        appSpecificLocaleBroadcastReceiver.assertReceivedBroadcastContains(TEST_APP_PACKAGE_2,
+                DEFAULT_LOCALES_2);
+
+        // Verify that lazy restore occurred upon package install.
+        assertLocalesForApp(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+
+        // APP2's entry is removed from the stage file after restore so nothing should be restored
+        // when APP2 is installed for the second time.
+        uninstall(TEST_APP_PACKAGE_2);
+        install(TEST_APP_APK_2);
+        assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+    }
+
+    /**
+     * Tests the scenario when an application is removed from the device.
+     *
+     * <p>The data for the uninstalled app should be removed from the next backup pass.
+     */
+    public void testBackupRestore_uninstallApp_deletesDataFromBackup() throws Exception {
+        if (!isBackupSupported()) {
+            return;
+        }
+        setAndBackupDefaultAppLocales();
+
+        // Uninstall an app and run the backup pass. The locales for the uninstalled app should
+        // be removed from the backup.
+        uninstall(TEST_APP_PACKAGE_2);
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+        getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE);
+
+        install(TEST_APP_APK_2);
+        // Remove app1's locales so that it can be restored.
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES);
+
+        getBackupUtils().restoreAndAssertSuccess(RESTORE_TOKEN, SYSTEM_PACKAGE);
+
+        // Restores only app1's locales because app2's data is no longer present in the backup.
+        assertLocalesForApp(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+        assertLocalesForApp(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+    }
+
+    // TODO(b/210593602): Add a test to check staged data removal after the retention period.
+
+    private void setApplicationLocalesAndVerify(String packageName, LocaleList locales)
+            throws Exception {
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(packageName, locales),
+                Manifest.permission.CHANGE_CONFIGURATION);
+        assertLocalesForApp(packageName, locales);
+    }
+
+    /**
+     * Verifies that the locales are correctly set for another package
+     * by fetching locales of the app with a binder call.
+     */
+    private void assertLocalesForApp(String packageName,
+            LocaleList expectedLocales) throws Exception {
+        assertEquals(expectedLocales, getApplicationLocales(packageName));
+    }
+
+    private LocaleList getApplicationLocales(String packageName) throws Exception {
+        return callWithShellPermissionIdentity(() ->
+                        mLocaleManager.getApplicationLocales(packageName),
+                Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+    }
+
+    private void install(String apk) {
+        ShellUtils.runShellCommand("pm install -r " + apk);
+    }
+
+    private void uninstall(String packageName) {
+        ShellUtils.runShellCommand("pm uninstall " + packageName);
+    }
+
+    private void setAndBackupDefaultAppLocales() throws Exception {
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, DEFAULT_LOCALES_1);
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, DEFAULT_LOCALES_2);
+        // Backup the data for SYSTEM_PACKAGE which includes app-locales.
+        getBackupUtils().backupNowAndAssertSuccess(SYSTEM_PACKAGE);
+    }
+
+    private void resetAppLocales() throws Exception {
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_1, EMPTY_LOCALES);
+        setApplicationLocalesAndVerify(TEST_APP_PACKAGE_2, EMPTY_LOCALES);
+    }
+
+    private static final class BlockingBroadcastReceiver extends BroadcastReceiver {
+        private CountDownLatch mLatch = new CountDownLatch(1);
+        private String mPackageName;
+        private LocaleList mLocales;
+        private int mCalls;
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) {
+                mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+            }
+            if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) {
+                mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST);
+            }
+            mCalls += 1;
+            mLatch.countDown();
+        }
+
+        public void await() throws Exception {
+            mLatch.await(/* timeout= */ 5, TimeUnit.SECONDS);
+        }
+
+        public void reset() {
+            mLatch = new CountDownLatch(1);
+            mCalls = 0;
+            mPackageName = null;
+            mLocales = null;
+        }
+
+        public void assertOneBroadcastReceived() {
+            assertEquals(1, mCalls);
+        }
+
+        /**
+         * Verifies that the broadcast received in the relevant apps have the correct information
+         * in the intent extras. It verifies the below extras:
+         * <ul>
+         * <li> {@link Intent#EXTRA_PACKAGE_NAME}
+         * <li> {@link Intent#EXTRA_LOCALE_LIST}
+         * </ul>
+         */
+        public void assertReceivedBroadcastContains(String expectedPackageName,
+                LocaleList expectedLocales) {
+            assertEquals(expectedPackageName, mPackageName);
+            assertEquals(expectedLocales, mLocales);
+        }
+    }
+}
diff --git a/tests/camera/AndroidManifest.xml b/tests/camera/AndroidManifest.xml
index 7426c65..ab3120a 100644
--- a/tests/camera/AndroidManifest.xml
+++ b/tests/camera/AndroidManifest.xml
@@ -89,6 +89,9 @@
             android:screenOrientation="locked"
             android:configChanges="keyboardHidden|orientation|screenSize">
         </activity>
+        <activity android:name="android.hardware.camera2.cts.EmptyActivity"
+            android:label="EmptyActivity">
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
index cf02ebe..feb5567 100644
--- a/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
+++ b/tests/camera/api31test/src/android/camera/cts/api31test/SPerfClassTest.java
@@ -206,9 +206,9 @@
     }
 
     /**
-     * Check camera S Performance class requirement for JPEG sizes.
+     * Check JPEG size overrides for devices claiming S Performance class requirement via
+     * Version.MEDIA_PERFORMANCE_CLASS
      */
-    @CddTest(requirement="7.5/H-1-8")
     public void testSPerfClassJpegSizes() throws Exception {
         boolean isSPerfClass = CameraTestUtils.isSPerfClass();
         if (!isSPerfClass) {
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 620ccee..5640249 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -646,6 +646,7 @@
             String expectedStr, String unExpectedStr) throws Exception {
         String candidateId = expectedEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
                 java.util.concurrent.TimeUnit.MILLISECONDS);
+        assertNotNull("No " + expectedStr + " notice for expected ID " + expectedId, candidateId);
         assertTrue("Received " + expectedStr + " notice for wrong ID, " +
                 "expected " + expectedId + ", got " + candidateId, expectedId.equals(candidateId));
         assertTrue("Received >  1 " + expectedStr + " callback for id " + expectedId,
@@ -859,6 +860,43 @@
                     candidatePhysicalIds == null);
         }
 
+        if (mAdoptShellPerm) {
+            // Open an arbitrary camera and make sure subsequently subscribed listener receives
+            // correct onCameraOpened/onCameraClosed callbacks
+
+            MockStateCallback mockListener = MockStateCallback.mock();
+            mCameraListener = new BlockingStateCallback(mockListener);
+
+            if (useExecutor) {
+                mCameraManager.openCamera(cameras[0], executor, mCameraListener);
+            } else {
+                mCameraManager.openCamera(cameras[0], mCameraListener, mHandler);
+            }
+
+            // Block until opened
+            mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
+                    CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+            // Then verify only open happened, and close the camera
+            CameraDevice camera = verifyCameraStateOpened(cameras[0], mockListener);
+
+            if (useExecutor) {
+                mCameraManager.registerAvailabilityCallback(executor, ac);
+            } else {
+                mCameraManager.registerAvailabilityCallback(ac, mHandler);
+            }
+
+            // Verify that we see the expected 'onCameraOpened' event.
+            verifySingleAvailabilityCbsReceived(onCameraOpenedEventQueue,
+                    onCameraClosedEventQueue, cameras[0], "onCameraOpened", "onCameraClosed");
+
+            camera.close();
+
+            mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
+                    CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
+
+            verifySingleAvailabilityCbsReceived(onCameraClosedEventQueue,
+                    onCameraOpenedEventQueue, cameras[0], "onCameraClosed", "onCameraOpened");
+        }
     } // testCameraManagerListenerCallbacks
 
     // Verify no LEGACY-level devices appear on devices first launched in the Q release or newer
diff --git a/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java b/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java
new file mode 100644
index 0000000..74e847e
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/EmptyActivity.java
@@ -0,0 +1,37 @@
+/*
+ * 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.camera2.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+
+public class EmptyActivity extends Activity {
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        super.onCreate(savedInstanceState, persistentState);
+        View view = new View(this);
+        view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+        setContentView(view);
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 28475ec..361bfda 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -16,7 +16,26 @@
 
 package android.hardware.camera2.cts;
 
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertArrayContains;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertArrayContainsAnyOf;
+import static android.hardware.camera2.cts.helpers.AssertHelpers.assertCollectionContainsAnyOf;
+import static android.hardware.cts.helpers.CameraUtils.matchParametersToCharacteristics;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
@@ -32,44 +51,50 @@
 import android.hardware.camera2.params.BlackLevelPattern;
 import android.hardware.camera2.params.ColorSpaceTransform;
 import android.hardware.camera2.params.DeviceStateSensorOrientationMap;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.cts.helpers.CameraUtils;
 import android.media.CamcorderProfile;
 import android.media.ImageReader;
 import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
 import android.util.ArraySet;
-import android.util.DisplayMetrics;
 import android.util.Log;
-import android.util.Rational;
-import android.util.Range;
-import android.util.Size;
 import android.util.Pair;
 import android.util.Patterns;
+import android.util.Range;
+import android.util.Rational;
+import android.util.Size;
 import android.view.Display;
 import android.view.Surface;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.rule.ActivityTestRule;
+
+import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.DeviceReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-import java.util.Set;
 
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
-import static android.hardware.cts.helpers.CameraUtils.matchParametersToCharacteristics;
-
-import static junit.framework.Assert.*;
-
-import static org.mockito.Mockito.*;
+import static android.hardware.camera2.cts.CameraTestUtils.MPC_REPORT_LOG_NAME;
+import static android.hardware.camera2.cts.CameraTestUtils.MPC_STREAM_NAME;
 
 /**
  * Extended tests for static camera characteristics.
@@ -142,6 +167,10 @@
     private static final int HIGH_SPEED_FPS_LOWER_MIN = 30;
     private static final int HIGH_SPEED_FPS_UPPER_MIN = 120;
 
+    @Rule
+    public final ActivityTestRule<EmptyActivity> mActivityRule = new ActivityTestRule<>(
+            EmptyActivity.class, false, false);
+
     @Override
     public void setUp() throws Exception {
         super.setUp();
@@ -817,7 +846,7 @@
 
             try {
                 RecommendedStreamConfigurationMap map = c.getRecommendedStreamConfigurationMap(
-                        RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT + 1);
+                        RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT + 1);
                 fail("Recommended configuration map shouldn't be available for invalid " +
                         "use case!");
             } catch (IllegalArgumentException e) {
@@ -845,6 +874,9 @@
             RecommendedStreamConfigurationMap lowLatencyConfig =
                     c.getRecommendedStreamConfigurationMap(
                     RecommendedStreamConfigurationMap.USECASE_LOW_LATENCY_SNAPSHOT);
+            RecommendedStreamConfigurationMap dynamic10BitOutputConfig =
+                    c.getRecommendedStreamConfigurationMap(
+                            RecommendedStreamConfigurationMap.USECASE_10BIT_OUTPUT);
             if ((previewConfig == null) && (videoRecordingConfig == null) &&
                     (videoSnapshotConfig == null) && (snapshotConfig == null) &&
                     (rawConfig == null) && (zslConfig == null) && (lowLatencyConfig == null)) {
@@ -886,6 +918,13 @@
             if (lowLatencyConfig != null) {
                 verifyRecommendedLowLatencyConfiguration(mAllCameraIds[i], c, lowLatencyConfig);
             }
+
+            if (dynamic10BitOutputConfig != null) {
+                verifyCommonRecommendedConfiguration(mAllCameraIds[i], c, dynamic10BitOutputConfig,
+                        /*checkNoInput*/ true, /*checkNoHighRes*/ false,
+                        /*checkNoHighSpeed*/ false, /*checkNoPrivate*/ false,
+                        /*checkNoDepth*/ true);
+            }
         }
     }
 
@@ -1778,6 +1817,76 @@
         }
     }
 
+    /**
+     * Check 10-Bit output capability
+     */
+    @Test
+    public void test10BitOutputCharacteristics() {
+        for (int i = 0; i < mAllCameraIds.length; i++) {
+            Log.i(TAG, "test10BitOutputCharacteristics: Testing camera ID " + mAllCameraIds[i]);
+
+            CameraCharacteristics c = mCharacteristics.get(i);
+            int[] capabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    capabilities);
+            boolean supports10BitOutput = arrayContains(capabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT);
+            if (!supports10BitOutput) {
+                continue;
+            }
+
+            DynamicRangeProfiles dynamicProfiles = c.get(
+                    CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+            mCollector.expectNotNull("Dynamic range profile must always be present in case " +
+                    "of 10-bit capable devices!", dynamicProfiles);
+            Set<Integer> supportedProfiles = dynamicProfiles.getSupportedProfiles();
+            mCollector.expectTrue("Dynamic range profiles not present!",
+                    !supportedProfiles.isEmpty());
+            // STANDARD and HLG10 must always be present in the supported profiles
+            mCollector.expectContains(supportedProfiles.toArray(), DynamicRangeProfiles.STANDARD);
+            mCollector.expectContains(supportedProfiles.toArray(), DynamicRangeProfiles.HLG10);
+
+            Integer recommendedProfile = c.get(
+                    CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE);
+            assertNotNull(recommendedProfile);
+            mCollector.expectContains(supportedProfiles.toArray(), recommendedProfile);
+            mCollector.expectTrue("The recommended 10-bit dynamic range profile must " +
+                            "not be the same as standard",
+                    recommendedProfile != DynamicRangeProfiles.STANDARD);
+
+            // Verify constraints validity. For example if HLG10 advertises support for HDR10, then
+            // there shouldn't be any HDR10 constraints related to HLG10.
+            for (Integer profile : supportedProfiles) {
+                Set<Integer> currentConstraints =
+                        dynamicProfiles.getProfileCaptureRequestConstraints(profile);
+                boolean isSameProfilePresent = false;
+                for (Integer concurrentProfile : currentConstraints) {
+                    if (concurrentProfile == profile) {
+                        isSameProfilePresent = true;
+                        continue;
+                    }
+                    String msg = String.format("Dynamic profile %d supports profile %d " +
+                                    "in the same capture request, however profile %d is not" +
+                                    "advertised as supported!", profile, concurrentProfile,
+                                    concurrentProfile);
+                    mCollector.expectTrue(msg, supportedProfiles.contains(concurrentProfile));
+
+                    Set<Integer> supportedConstraints =
+                            dynamicProfiles.getProfileCaptureRequestConstraints(concurrentProfile);
+                    msg = String.format("Dynamic range profile %d advertises support " +
+                                    "for profile %d, however the opposite is not true!",
+                                    profile, concurrentProfile);
+                    mCollector.expectTrue(msg, supportedConstraints.isEmpty() ||
+                            supportedConstraints.contains(profile));
+                }
+
+                String msg = String.format("Dynamic profile %d not present in its advertised " +
+                        "capture request constraints!", profile);
+                mCollector.expectTrue(msg, isSameProfilePresent || currentConstraints.isEmpty());
+            }
+        }
+    }
+
     private void verifyLensCalibration(float[] poseRotation, float[] poseTranslation,
             Integer poseReference, float[] cameraIntrinsics, float[] distortion,
             Rect precorrectionArray, Integer facing) {
@@ -2567,28 +2676,45 @@
     /**
      * Check camera orientation against device orientation
      */
-    @CddTest(requirement="7.5.5/C-1-1")
+    @AppModeFull(reason = "DeviceStateManager is not accessible to instant apps")
+    @CddTest(requirement = "7.5.5/C-1-1")
     @Test
     public void testCameraOrientationAlignedWithDevice() {
-        WindowManager windowManager =
-                (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-        Display display = windowManager.getDefaultDisplay();
-        DisplayMetrics metrics = new DisplayMetrics();
-        display.getMetrics(metrics);
+        if (CameraUtils.isDeviceFoldable(mContext)) {
+            // CDD 7.5.5/C-1-1 does not apply to devices with folding displays as the display aspect
+            // ratios might change with the device's folding state.
+            // Skip this test in foldables until the CDD is updated to include foldables.
+            Log.i(TAG, "CDD 7.5.5/C-1-1 does not apply to foldables, skipping"
+                    + " testCameraOrientationAlignedWithDevice");
+            return;
+        }
+
+        // Start the empty activty to check the display we're testing.
+        mActivityRule.launchActivity(new Intent());
+        Context foregroundActivity = mActivityRule.getActivity();
+
+        WindowManager windowManager = foregroundActivity.getSystemService(WindowManager.class);
+        assertNotNull("Could not get window manager for test activity.", windowManager);
+
+        WindowMetrics metrics = windowManager.getMaximumWindowMetrics();
+        Rect displayBounds = metrics.getBounds();
+        int widthPixels = displayBounds.width();
+        int heightPixels = displayBounds.height();
 
         // For square screen, test is guaranteed to pass
-        if (metrics.widthPixels == metrics.heightPixels) {
+        if (widthPixels == heightPixels) {
             return;
         }
 
         // Handle display rotation
+        Display display = foregroundActivity.getDisplay();
         int displayRotation = display.getRotation();
         if (displayRotation == Surface.ROTATION_90 || displayRotation == Surface.ROTATION_270) {
-            int tmp = metrics.widthPixels;
-            metrics.widthPixels = metrics.heightPixels;
-            metrics.heightPixels = tmp;
+            int tmp = widthPixels;
+            widthPixels = heightPixels;
+            heightPixels = tmp;
         }
-        boolean isDevicePortrait = metrics.widthPixels < metrics.heightPixels;
+        boolean isDevicePortrait = widthPixels < heightPixels;
 
         for (int i = 0; i < mAllCameraIds.length; i++) {
             CameraCharacteristics c = mCharacteristics.get(i);
@@ -2613,28 +2739,62 @@
 
             boolean isCameraPortrait =
                     adjustedSensorSize.getWidth() < adjustedSensorSize.getHeight();
-            assertFalse("Camera " + mAllCameraIds[i] + "'s long dimension must "
-                    + "align with screen's long dimension", isDevicePortrait^isCameraPortrait);
+
+            // device and camera orientation should either be both portrait, or both landscape
+            assertEquals("Camera " + mAllCameraIds[i] + "'s long dimension must "
+                    + "align with screen's long dimension", isDevicePortrait, isCameraPortrait);
         }
     }
 
     /**
+     * If meetPerfClass is true, return perfClassLevel.
+     * Otherwise, return NOT_MET.
+     */
+    private int updatePerfClassLevel(boolean meetPerfClass, int perfClassLevel) {
+        if (!meetPerfClass) {
+            return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+        } else {
+            return perfClassLevel;
+        }
+    }
+
+    /**
+     * Update perf class level based on meetSPerfClass and meetRPerfClass.
+     */
+    private int updatePerfClassLevel(boolean meetSPerfClass, boolean meetRPerfClass,
+            int perfClassLevel) {
+        if (!meetRPerfClass) {
+            return CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+        } else if (!meetSPerfClass &&
+                perfClassLevel > CameraTestUtils.PERFORMANCE_CLASS_R) {
+            return CameraTestUtils.PERFORMANCE_CLASS_R;
+        }
+        return perfClassLevel;
+    }
+
+    /**
      * Check camera characteristics for R and S Performance class requirements as specified
      * in CDD camera section 7.5
      */
     @Test
-    @CddTest(requirement="7.5")
+    @CddTest(requirement="7.5/H-1-1,H-1-2,H-1-3,H-1-4,H-1-8")
     public void testCameraPerfClassCharacteristics() throws Exception {
         if (mAdoptShellPerm) {
             // Skip test for system camera. Performance class is only applicable for public camera
             // ids.
             return;
         }
-        boolean isRPerfClass = CameraTestUtils.isRPerfClass();
-        boolean isSPerfClass = CameraTestUtils.isSPerfClass();
-        if (!isRPerfClass && !isSPerfClass) {
-            return;
-        }
+        boolean assertRPerfClass = CameraTestUtils.isRPerfClass();
+        boolean assertSPerfClass = CameraTestUtils.isSPerfClass();
+        boolean assertPerfClass = (assertRPerfClass || assertSPerfClass);
+
+        int perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+        int perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+        int perfClassLevelH13 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+        int perfClassLevelH14 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+        int perfClassLevelH18 = CameraTestUtils.PERFORMANCE_CLASS_CURRENT;
+
+        DeviceReportLog reportLog = new DeviceReportLog(MPC_REPORT_LOG_NAME, MPC_STREAM_NAME);
 
         boolean hasPrimaryRear = false;
         boolean hasPrimaryFront = false;
@@ -2663,78 +2823,145 @@
 
             if (isPrimaryRear) {
                 hasPrimaryRear = true;
-                mCollector.expectTrue("Primary rear camera resolution should be at least " +
-                        MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
-                        sensorResolution,
-                        sensorResolution >= MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION);
+                if (sensorResolution < MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION) {
+                    mCollector.expectTrue("Primary rear camera resolution should be at least " +
+                            MIN_BACK_SENSOR_PERF_CLASS_RESOLUTION + " pixels, is "+
+                            sensorResolution, !assertPerfClass);
+                    perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                }
+                reportLog.addValue("rear camera resolution", sensorResolution,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
 
                 // 4K @ 30fps
                 boolean supportUHD = videoSizes.contains(UHD);
                 boolean supportDC4K = videoSizes.contains(DC4K);
-                mCollector.expectTrue("Primary rear camera should support 4k video recording",
-                        supportUHD || supportDC4K);
-                if (supportUHD || supportDC4K) {
+                reportLog.addValue("rear camera 4k support", supportUHD | supportDC4K,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
+                if (!supportUHD && !supportDC4K) {
+                    mCollector.expectTrue("Primary rear camera should support 4k video recording",
+                            !assertPerfClass);
+                    perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                } else {
                     long minFrameDuration = config.getOutputMinFrameDuration(
                             android.media.MediaRecorder.class, supportDC4K ? DC4K : UHD);
-                    mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
-                            minFrameDuration < (1e9 / 29.9));
+                    reportLog.addValue("rear camera 4k frame duration", minFrameDuration,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
+                    if (minFrameDuration >= (1e9 / 29.9)) {
+                        mCollector.expectTrue("Primary rear camera should support 4k video @ 30fps",
+                                !assertPerfClass);
+                        perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                    }
                 }
             } else {
                 hasPrimaryFront = true;
-                if (isSPerfClass) {
+                if (sensorResolution < MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION) {
+                    mCollector.expectTrue("Primary front camera resolution should be at least " +
+                        MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
+                        sensorResolution, !assertSPerfClass);
+                    perfClassLevelH12 = Math.min(
+                            perfClassLevelH12, CameraTestUtils.PERFORMANCE_CLASS_R);
+                }
+                if (sensorResolution < MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION) {
                     mCollector.expectTrue("Primary front camera resolution should be at least " +
                             MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION + " pixels, is "+
-                            sensorResolution,
-                            sensorResolution >= MIN_FRONT_SENSOR_S_PERF_CLASS_RESOLUTION);
-                } else {
-                    mCollector.expectTrue("Primary front camera resolution should be at least " +
-                            MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION + " pixels, is "+
-                            sensorResolution,
-                            sensorResolution >= MIN_FRONT_SENSOR_R_PERF_CLASS_RESOLUTION);
+                            sensorResolution, !assertRPerfClass);
+                    perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
                 }
+                reportLog.addValue("front camera resolution", sensorResolution,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
+
                 // 1080P @ 30fps
                 boolean supportFULLHD = videoSizes.contains(FULLHD);
-                mCollector.expectTrue("Primary front camera should support 1080P video recording",
-                        supportFULLHD);
-                if (supportFULLHD) {
+                reportLog.addValue("front camera 1080p support", supportFULLHD,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
+                if (!supportFULLHD) {
+                    mCollector.expectTrue(
+                            "Primary front camera should support 1080P video recording",
+                            !assertPerfClass);
+                    perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                } else {
                     long minFrameDuration = config.getOutputMinFrameDuration(
                             android.media.MediaRecorder.class, FULLHD);
-                    mCollector.expectTrue("Primary front camera should support 1080P video @ 30fps",
-                            minFrameDuration < (1e9 / 29.9));
+                    if (minFrameDuration >= (1e9 / 29.9)) {
+                        mCollector.expectTrue(
+                                "Primary front camera should support 1080P video @ 30fps",
+                                !assertPerfClass);
+                        perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+                    }
+                    reportLog.addValue("front camera 1080p frame duration", minFrameDuration,
+                        ResultType.NEUTRAL, ResultUnit.NONE);
                 }
             }
 
-            String facingString = hasPrimaryRear ? "rear" : "front";
+            String facingString = isPrimaryRear ? "rear" : "front";
             // H-1-3
-            if (isSPerfClass || (isRPerfClass && isPrimaryRear)) {
+            if (assertSPerfClass || (assertRPerfClass && isPrimaryRear)) {
                 mCollector.expectTrue("Primary " + facingString +
                         " camera should be at least FULL, but is " +
                         toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
                         staticInfo.isHardwareLevelAtLeastFull());
-            } else {
+            } else if (assertRPerfClass) {
                 mCollector.expectTrue("Primary " + facingString +
                         " camera should be at least LIMITED, but is " +
                         toStringHardwareLevel(staticInfo.getHardwareLevelChecked()),
                         staticInfo.isHardwareLevelAtLeastLimited());
             }
 
+            reportLog.addValue(facingString + " camera hardware level",
+                    staticInfo.getHardwareLevelChecked(), ResultType.NEUTRAL, ResultUnit.NONE);
+            if (isPrimaryRear) {
+                perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
+                        perfClassLevelH13);
+            } else {
+                perfClassLevelH13 = updatePerfClassLevel(staticInfo.isHardwareLevelAtLeastFull(),
+                        staticInfo.isHardwareLevelAtLeastLimited(), perfClassLevelH13);
+            }
+
             // H-1-4
             Integer timestampSource = c.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+            reportLog.addValue(facingString + " timestampSource",
+                    timestampSource, ResultType.NEUTRAL, ResultUnit.NONE);
+            boolean realtimeTimestamp = (timestampSource != null &&
+                    timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
             mCollector.expectTrue(
                     "Primary " + facingString + " camera should support real-time timestamp source",
-                    timestampSource != null &&
-                    timestampSource.equals(CameraMetadata.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME));
+                    !assertPerfClass || realtimeTimestamp);
+            perfClassLevelH14 = updatePerfClassLevel(realtimeTimestamp, perfClassLevelH14);
 
             // H-1-8
-            if (isSPerfClass && isPrimaryRear) {
+            if (isPrimaryRear) {
+                boolean supportRaw = staticInfo.isCapabilitySupported(RAW);
+                reportLog.addValue(facingString + " camera raw support",
+                        supportRaw, ResultType.NEUTRAL, ResultUnit.NONE);
                 mCollector.expectTrue("Primary rear camera should support RAW capability",
-                        staticInfo.isCapabilitySupported(RAW));
+                        !assertSPerfClass || supportRaw);
+                perfClassLevelH18 = updatePerfClassLevel(supportRaw, true /*R*/, perfClassLevelH18);
             }
         }
-        mCollector.expectTrue("There must be a primary rear camera for performance class.",
-                hasPrimaryRear);
-        mCollector.expectTrue("There must be a primary front camera for performance class.",
-                hasPrimaryFront);
+        if (!hasPrimaryRear) {
+            mCollector.expectTrue("There must be a primary rear camera for performance class.",
+                    !assertPerfClass);
+            perfClassLevelH11 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+        }
+        if (!hasPrimaryFront) {
+            mCollector.expectTrue("There must be a primary front camera for performance class.",
+                    !assertPerfClass);
+            perfClassLevelH12 = CameraTestUtils.PERFORMANCE_CLASS_NOT_MET;
+        }
+
+        reportLog.addValue("Version", "0.0.1", ResultType.NEUTRAL, ResultUnit.NONE);
+        final String PERF_CLASS_REQ_NUM_PREFIX = "2.2.7.2/7.5/";
+        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-1",
+                perfClassLevelH11, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-2",
+                perfClassLevelH12, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-3",
+                perfClassLevelH13, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-4",
+                perfClassLevelH14, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.addValue(PERF_CLASS_REQ_NUM_PREFIX + "H-1-8",
+                perfClassLevelH18, ResultType.NEUTRAL, ResultUnit.NONE);
+        reportLog.submit(InstrumentationRegistry.getInstrumentation());
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
index 9b84387..82760f0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/FlashlightTest.java
@@ -17,6 +17,7 @@
 package android.hardware.camera2.cts;
 
 import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
@@ -50,6 +51,7 @@
     private static final int NUM_REGISTERS = 10;
 
     private ArrayList<String> mFlashCameraIdList;
+    private ArrayList<String> mNoFlashCameraIdList;
 
     @Override
     public void setUp() throws Exception {
@@ -60,17 +62,101 @@
         // initialize the list of cameras that have a flash unit so it won't interfere with
         // flash tests.
         mFlashCameraIdList = new ArrayList<String>();
+        mNoFlashCameraIdList = new ArrayList<String>();
         for (String id : mCameraIdsUnderTest) {
             StaticMetadata info =
                     new StaticMetadata(mCameraManager.getCameraCharacteristics(id),
                                        CheckLevel.ASSERT, /*collector*/ null);
             if (info.hasFlash()) {
                 mFlashCameraIdList.add(id);
+            } else  {
+                mNoFlashCameraIdList.add(id);
             }
         }
     }
 
     @Test
+    public void testTurnOnTorchWithStrengthLevel() throws Exception {
+        if (mNoFlashCameraIdList.size() != 0) {
+            for (String id : mNoFlashCameraIdList) {
+                CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
+                assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL));
+                assertNull(pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL));
+            }
+        }
+
+        if (mFlashCameraIdList.size() == 0)
+            return;
+
+        for (String id : mFlashCameraIdList) {
+            resetTorchModeStatus(id);
+        }
+
+        for (String id: mFlashCameraIdList) {
+            int maxLevel = 0;
+            int defaultLevel = 0;
+            int minLevel = 1;
+            CameraCharacteristics pc = mCameraManager.getCameraCharacteristics(id);
+            if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL) != null) {
+                defaultLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL);
+            }
+            if (pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL) != null) {
+                maxLevel = pc.get(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL);
+            }
+            if (maxLevel > 1) {
+                assertTrue(minLevel <= defaultLevel);
+                assertTrue(defaultLevel <= maxLevel);
+                int torchStrength = 0;
+                CameraManager.TorchCallback torchListener = mock(CameraManager.TorchCallback.class);
+                mCameraManager.registerTorchCallback(torchListener, mHandler);
+
+                mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
+                SystemClock.sleep(TORCH_DURATION_MS);
+                torchStrength = mCameraManager.getTorchStrengthLevel(id);
+                assertEquals(torchStrength, maxLevel);
+                // Calling with same value twice to verify onTorchStrengthLevelChanged()
+                // with maxLevel value is called only once.
+                mCameraManager.turnOnTorchWithStrengthLevel(id, maxLevel);
+                torchStrength = mCameraManager.getTorchStrengthLevel(id);
+                assertEquals(torchStrength, maxLevel);
+
+                mCameraManager.turnOnTorchWithStrengthLevel(id, defaultLevel);
+                torchStrength = mCameraManager.getTorchStrengthLevel(id);
+                assertEquals(torchStrength, defaultLevel);
+
+                mCameraManager.turnOnTorchWithStrengthLevel(id, minLevel);
+                torchStrength = mCameraManager.getTorchStrengthLevel(id);
+                assertEquals(torchStrength, minLevel);
+
+                // Turn off the torch and verify if the strength level gets
+                // reset to default level.
+                mCameraManager.setTorchMode(id, false);
+                torchStrength = mCameraManager.getTorchStrengthLevel(id);
+                assertEquals(torchStrength, defaultLevel);
+
+                // verify corrected numbers of callbacks
+                verify(torchListener, timeout(TORCH_TIMEOUT_MS).
+                        times(1)).onTorchModeChanged(id, true);
+
+                verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+                        times(1)).onTorchStrengthLevelChanged(id, maxLevel);
+                verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+                        times(1)).onTorchStrengthLevelChanged(id, minLevel);
+                verify(torchListener,timeout(TORCH_TIMEOUT_MS).
+                        times(1)).onTorchStrengthLevelChanged(id, defaultLevel);
+
+                verify(torchListener, timeout(TORCH_TIMEOUT_MS).
+                        times(2)).onTorchModeChanged(id, false);
+
+                mCameraManager.unregisterTorchCallback(torchListener);
+            } else {
+                Log.i(TAG, "Torch strength level adjustment is not supported.");
+            }
+        }
+    }
+
+
+    @Test
     public void testSetTorchModeOnOff() throws Exception {
         if (mFlashCameraIdList.size() == 0)
             return;
@@ -363,6 +449,8 @@
         private String mCameraId;
         private ArrayBlockingQueue<Integer> mStatusQueue =
                 new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
+        private ArrayBlockingQueue<Integer> mTorchStrengthQueue =
+                new ArrayBlockingQueue<Integer>(QUEUE_CAPACITY);
 
         public static final int STATUS_UNAVAILABLE = 0;
         public static final int STATUS_OFF = 1;
@@ -397,6 +485,17 @@
         }
 
         @Override
+        public void onTorchStrengthLevelChanged(String cameraId, int newStrengthLevel) {
+            if (cameraId.equals(mCameraId)) {
+                try {
+                    mTorchStrengthQueue.put(newStrengthLevel);
+                } catch (Throwable e) {
+                    fail(e.getMessage());
+                }
+            }
+        }
+
+        @Override
         public void onTorchModeChanged(String cameraId, boolean enabled) {
             if (cameraId.equals(mCameraId)) {
                 Integer s;
diff --git a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
index a852729..d45a91b 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -16,7 +16,21 @@
 
 package android.hardware.camera2.cts;
 
-import android.content.Context;
+import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
+import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
@@ -27,6 +41,7 @@
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.hardware.DataSpace;
 import android.hardware.HardwareBuffer;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -36,11 +51,13 @@
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.rs.BitmapUtils;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
+import android.media.ImageWriter;
 import android.os.ConditionVariable;
 import android.util.Log;
 import android.util.Size;
@@ -48,25 +65,16 @@
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.Test;
-
-import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
-import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
-import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
-import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
-import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
-import static com.google.common.truth.Truth.assertWithMessage;
-import static junit.framework.Assert.*;
-
 /**
  * <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
  * sends the data to the surface provided by imageReader. Below image formats
@@ -223,7 +231,18 @@
             try {
                 Log.v(TAG, "Testing YUV P010 capture for Camera " + id);
                 openDevice(id);
-                bufferFormatTestByCamera(ImageFormat.YCBCR_P010, /*repeating*/false);
+                if (!mStaticInfo.isCapabilitySupported(CameraCharacteristics.
+                            REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
+                    continue;
+                }
+                Set<Integer> availableProfiles =
+                    mStaticInfo.getAvailableDynamicRangeProfilesChecked();
+                assertFalse("Absent dynamic range profiles", availableProfiles.isEmpty());
+                assertTrue("HLG10 not present in the available dynamic range profiles",
+                        availableProfiles.contains(DynamicRangeProfiles.HLG10));
+
+                bufferFormatTestByCamera(ImageFormat.YCBCR_P010, /*repeating*/false,
+                    DynamicRangeProfiles.HLG10);
             } finally {
                 closeDevice(id);
             }
@@ -404,6 +423,76 @@
         }
     }
 
+    @Test
+    public void testImageReaderBuilderSetHardwareBufferFormatAndDataSpace() throws Exception {
+        long usage = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE | HardwareBuffer.USAGE_GPU_COLOR_OUTPUT;
+        try (
+            ImageReader reader = new ImageReader
+                .Builder(20, 45)
+                .setMaxImages(2)
+                .setDefaultHardwareBufferFormat(HardwareBuffer.RGB_888)
+                .setDefaultDataSpace(DataSpace.DATASPACE_BT709)
+                .setUsage(usage)
+                .build();
+            ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+            Image outputImage = writer.dequeueInputImage()
+        ) {
+            assertEquals(2, reader.getMaxImages());
+            assertEquals(usage, reader.getUsage());
+            assertEquals(HardwareBuffer.RGB_888, reader.getHardwareBufferFormat());
+
+            assertEquals(20, outputImage.getWidth());
+            assertEquals(45, outputImage.getHeight());
+            assertEquals(HardwareBuffer.RGB_888, outputImage.getFormat());
+        }
+    }
+
+    @Test
+    public void testImageReaderBuilderImageFormatOverride() throws Exception {
+        try (
+            ImageReader reader = new ImageReader
+                .Builder(20, 45)
+                .setImageFormat(ImageFormat.HEIC)
+                .setDefaultHardwareBufferFormat(HardwareBuffer.RGB_888)
+                .setDefaultDataSpace(DataSpace.DATASPACE_BT709)
+                .build();
+            ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+            Image outputImage = writer.dequeueInputImage()
+        ) {
+            assertEquals(1, reader.getMaxImages());
+            assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
+            assertEquals(HardwareBuffer.RGB_888, reader.getHardwareBufferFormat());
+            assertEquals(DataSpace.DATASPACE_BT709, reader.getDataSpace());
+
+            assertEquals(20, outputImage.getWidth());
+            assertEquals(45, outputImage.getHeight());
+            assertEquals(HardwareBuffer.RGB_888, outputImage.getFormat());
+        }
+    }
+
+    @Test
+    public void testImageReaderBuilderSetImageFormat() throws Exception {
+        try (
+            ImageReader reader = new ImageReader
+                .Builder(20, 45)
+                .setMaxImages(2)
+                .setImageFormat(ImageFormat.YUV_420_888)
+                .build();
+            ImageWriter writer = ImageWriter.newInstance(reader.getSurface(), 1);
+            Image outputImage = writer.dequeueInputImage()
+        ) {
+            assertEquals(2, reader.getMaxImages());
+            assertEquals(ImageFormat.YUV_420_888, reader.getImageFormat());
+            assertEquals(HardwareBuffer.USAGE_CPU_READ_OFTEN, reader.getUsage());
+            // ImageFormat.YUV_420_888 hal dataspace is DATASPACE_JFIF
+            assertEquals(DataSpace.DATASPACE_JFIF, reader.getDataSpace());
+
+            assertEquals(20, outputImage.getWidth());
+            assertEquals(45, outputImage.getHeight());
+            assertEquals(ImageFormat.YUV_420_888, outputImage.getFormat());
+        }
+    }
+
     /**
      * Test two image stream (YUV420_888 and RAW_SENSOR) capture by using ImageReader.
      *
@@ -1076,6 +1165,13 @@
                 /*checkSession*/ false, /*validateImageData*/ true);
     }
 
+    private void bufferFormatTestByCamera(int format, boolean repeating, int dynamicRangeProfile)
+            throws Exception {
+        bufferFormatTestByCamera(format, /*setUsageFlag*/ false,
+                HardwareBuffer.USAGE_CPU_READ_OFTEN, repeating, /*checkSession*/ false,
+                /*validateImageData*/ true, /*physicalId*/null, dynamicRangeProfile);
+    }
+
     private void bufferFormatTestByCamera(int format, boolean repeating, boolean checkSession)
             throws Exception {
         bufferFormatTestByCamera(format, /*setUsageFlag*/ false,
@@ -1090,9 +1186,17 @@
     }
 
     private void bufferFormatTestByCamera(int format, boolean setUsageFlag, long usageFlag,
+            boolean repeating, boolean checkSession, boolean validateImageData, String physicalId)
+            throws Exception {
+        bufferFormatTestByCamera(format, setUsageFlag, usageFlag, repeating, checkSession,
+                validateImageData, physicalId, DynamicRangeProfiles.STANDARD);
+    }
+
+    private void bufferFormatTestByCamera(int format, boolean setUsageFlag, long usageFlag,
             // TODO: Consider having some sort of test configuration class passed to reduce the
             //       proliferation of parameters ?
-            boolean repeating, boolean checkSession, boolean validateImageData, String physicalId)
+            boolean repeating, boolean checkSession, boolean validateImageData, String physicalId,
+            int dynamicRangeProfile)
             throws Exception {
         StaticMetadata staticInfo;
         if (physicalId == null) {
@@ -1150,6 +1254,7 @@
                 if (physicalId != null) {
                     config.setPhysicalCameraId(physicalId);
                 }
+                config.setDynamicRangeProfile(dynamicRangeProfile);
                 outputConfigs.add(config);
                 CaptureRequest request = prepareCaptureRequestForConfigs(
                         outputConfigs, CameraDevice.TEMPLATE_PREVIEW).build();
diff --git a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
index 93826cb..0cc1135 100644
--- a/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -16,7 +16,28 @@
 
 package android.hardware.camera2.cts;
 
-import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.CameraTestUtils.ImageDropperListener;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.assertFalse;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertTrue;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig;
+import static android.hardware.camera2.cts.CameraTestUtils.fail;
+import static android.hardware.camera2.cts.CameraTestUtils.getCropRegionForZoom;
+import static android.hardware.camera2.cts.CameraTestUtils.getMaxPreviewSize;
+import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound;
+import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.Intent;
@@ -24,24 +45,19 @@
 import android.graphics.ImageFormat;
 import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
-import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.CaptureFailure;
-import android.hardware.camera2.cts.CaptureResultTest;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
-import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
-import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.BatteryManager;
@@ -52,29 +68,24 @@
 import android.util.Range;
 import android.util.Size;
 import android.util.SizeF;
-import android.view.Display;
-import android.view.Surface;
 import android.view.WindowManager;
 
 import com.android.compatibility.common.util.CddTest;
-import com.android.compatibility.common.util.Stat;
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.utils.StateWaiter;
 
-import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
 import java.util.ArrayList;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static org.mockito.Mockito.*;
+import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * Tests exercising logical camera setup, configuration, and usage.
@@ -941,24 +952,24 @@
                 continue;
             }
             StreamConfigurationMap configMap =
-                properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+                    properties.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
             physicalConfigs.put(physicalCameraId, configMap);
             physicalPreviewSizesMap.put(physicalCameraId,
                     getSupportedPreviewSizes(physicalCameraId, mCameraManager, PREVIEW_SIZE_BOUND));
         }
 
         // Find display size from window service.
-        Context context = mActivityRule.getActivity().getApplicationContext();
+        Context context = mActivityRule.getActivity();
         WindowManager windowManager =
                 (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        Display display = windowManager.getDefaultDisplay();
+        Rect windowBounds = windowManager.getCurrentWindowMetrics().getBounds();
 
-        int displayWidth = display.getWidth();
-        int displayHeight = display.getHeight();
+        int windowWidth = windowBounds.width();
+        int windowHeight = windowBounds.height();
 
-        if (displayHeight > displayWidth) {
-            displayHeight = displayWidth;
-            displayWidth = display.getHeight();
+        if (windowHeight > windowWidth) {
+            windowHeight = windowWidth;
+            windowWidth = windowBounds.height();
         }
 
         StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
@@ -966,27 +977,27 @@
         for (Size previewSize : previewSizes) {
             dualPhysicalCameraIds.clear();
             // Skip preview sizes larger than screen size
-            if (previewSize.getWidth() > displayWidth ||
-                    previewSize.getHeight() > displayHeight) {
+            if (previewSize.getWidth() > windowWidth
+                    || previewSize.getHeight() > windowHeight) {
                 continue;
             }
 
             final long minFrameDuration = config.getOutputMinFrameDuration(
-                   ImageFormat.YUV_420_888, previewSize);
+                    ImageFormat.YUV_420_888, previewSize);
 
             ArrayList<String> supportedPhysicalCameras = new ArrayList<String>();
             for (String physicalCameraId : physicalCameraIds) {
                 List<Size> physicalPreviewSizes = physicalPreviewSizesMap.get(physicalCameraId);
                 if (physicalPreviewSizes != null && physicalPreviewSizes.contains(previewSize)) {
-                   long minDurationPhysical =
-                           physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
-                           ImageFormat.YUV_420_888, previewSize);
-                   if (minDurationPhysical <= minFrameDuration) {
+                    long minDurationPhysical =
+                            physicalConfigs.get(physicalCameraId).getOutputMinFrameDuration(
+                                    ImageFormat.YUV_420_888, previewSize);
+                    if (minDurationPhysical <= minFrameDuration) {
                         dualPhysicalCameraIds.add(physicalCameraId);
                         if (dualPhysicalCameraIds.size() == 2) {
                             return previewSize;
                         }
-                   }
+                    }
                 }
             }
         }
@@ -1308,10 +1319,9 @@
     }
 
     private double getScreenSizeInInches() {
-        DisplayMetrics dm = new DisplayMetrics();
-        mWindowManager.getDefaultDisplay().getMetrics(dm);
-        double widthInInchesSquared = Math.pow(dm.widthPixels/dm.xdpi,2);
-        double heightInInchesSquared = Math.pow(dm.heightPixels/dm.ydpi,2);
+        DisplayMetrics dm = mContext.getResources().getDisplayMetrics();
+        double widthInInchesSquared = Math.pow(dm.widthPixels / dm.xdpi, 2);
+        double heightInInchesSquared = Math.pow(dm.heightPixels / dm.ydpi, 2);
         return Math.sqrt(widthInInchesSquared + heightInInchesSquared);
     }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index 492cc68..9e05266 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -16,58 +16,89 @@
 
 package android.hardware.camera2.cts;
 
-import static android.hardware.camera2.cts.CameraTestUtils.*;
-import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.*;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SessionConfigSupport;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.SizeComparator;
+import static android.hardware.camera2.cts.CameraTestUtils.StreamCombinationTargets;
+import static android.hardware.camera2.cts.CameraTestUtils.assertEquals;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationSupported;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationWithSurfaces;
+import static android.hardware.camera2.cts.CameraTestUtils.configureReprocessableCameraSession;
+import static android.hardware.camera2.cts.CameraTestUtils.fail;
+import static android.hardware.camera2.cts.CameraTestUtils.getAscendingOrderSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.JPEG;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.MAXIMUM;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.PREVIEW;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.PRIV;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.RAW;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.RECORD;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.VGA;
+import static android.hardware.camera2.cts.RobustnessTest.MaxStreamSizes.YUV;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.graphics.ImageFormat;
+import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.OisSample;
-import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.MandatoryStreamCombination;
 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
+import android.hardware.camera2.params.OisSample;
+import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.ImageWriter;
+import android.util.ArraySet;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
-import android.view.Display;
 import android.view.Surface;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 
-import java.util.Arrays;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Comparator;
-import java.util.concurrent.LinkedBlockingQueue;
+import java.util.Iterator;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
-import org.junit.runners.Parameterized;
-import org.junit.runner.RunWith;
-import org.junit.Test;
-
-import static junit.framework.Assert.assertTrue;
-import static org.mockito.Mockito.*;
+import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * Tests exercising edge cases in camera setup, configuration, and usage.
@@ -469,6 +500,173 @@
     }
 
     /**
+     * Test for making sure the required 10-bit stream combinations work as expected.
+     * Since we have too many possible combinations between different 8-bit vs. 10-bit as well
+     * as 10-bit dynamic profiles and in order to maximize the coverage within some reasonable
+     * amount of iterations, the test case will configure 8-bit and 10-bit outputs randomly. In case
+     * we have 10-bit output, then the dynamic range profile will also be randomly picked.
+     */
+    @Test
+    public void testMandatory10BitStreamCombinations() throws Exception {
+        for (String id : mCameraIdsUnderTest) {
+            openDevice(id);
+            CameraCharacteristics chars = mStaticInfo.getCharacteristics();
+            if (!CameraTestUtils.hasCapability(
+                    chars, CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT)) {
+                Log.i(TAG, "Camera id " + id + " doesn't support 10-bit output, skip test");
+                closeDevice(id);
+                continue;
+            }
+            CameraCharacteristics.Key<MandatoryStreamCombination []> ck =
+                    CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS;
+
+            MandatoryStreamCombination[] combinations = chars.get(ck);
+            assertNotNull(combinations);
+
+            try {
+                for (MandatoryStreamCombination combination : combinations) {
+                    Log.i(TAG, "Testing fixed mandatory 10-bit output stream combination: " +
+                            combination.getDescription() + " on camera: " + id);
+                    DynamicRangeProfiles profiles = mStaticInfo.getCharacteristics().get(
+                            CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+                    assertNotNull(profiles);
+
+                    // First we want to make sure that a fixed set of 10-bit streams
+                    // is functional
+                    for (Integer profile : profiles.getSupportedProfiles()) {
+                        if (profile != DynamicRangeProfiles.STANDARD) {
+                            ArrayList<Integer> testProfiles = new ArrayList<Integer>() {
+                                { add(profile); } };
+                            testMandatory10BitStreamCombination(id, combination, profiles,
+                                    testProfiles);
+                        }
+                    }
+
+                    Log.i(TAG, "Testing random mandatory 10-bit output stream combination: " +
+                            combination.getDescription() + " on camera: " + id);
+                    // Next try out a random mix of standard 8-bit and 10-bit profiles.
+                    // The number of possible combinations is quite big and testing them
+                    // all on physical hardware can become unfeasible.
+                    ArrayList<Integer> testProfiles = new ArrayList<>(
+                            profiles.getSupportedProfiles());
+                    testMandatory10BitStreamCombination(id, combination, profiles, testProfiles);
+                }
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    private void testMandatory10BitStreamCombination(String cameraId,
+            MandatoryStreamCombination combination, DynamicRangeProfiles profiles,
+            List<Integer> testProfiles) {
+        final int TIMEOUT_FOR_RESULT_MS = 1000;
+        final int MIN_RESULT_COUNT = 3;
+
+        // Setup outputs
+        List<OutputConfiguration> outputConfigs = new ArrayList<>();
+        List<Surface> outputSurfaces = new ArrayList<Surface>();
+        List<Surface> uhOutputSurfaces = new ArrayList<Surface>();
+        StreamCombinationTargets targets = new StreamCombinationTargets();
+
+        CameraTestUtils.setupConfigurationTargets(combination.getStreamsInformation(),
+                targets, outputConfigs, outputSurfaces, uhOutputSurfaces, MIN_RESULT_COUNT,
+                /*substituteY8*/ false, /*substituteHeic*/false,
+                /*physicalCameraId*/ null,
+                /*multiResStreamConfig*/null, mHandler,
+                testProfiles);
+
+        try {
+            checkSessionConfigurationSupported(mCamera, mHandler, outputConfigs,
+                    /*inputConfig*/ null, SessionConfiguration.SESSION_REGULAR,
+                    true/*defaultSupport*/,
+                    String.format("Session configuration query from combination: %s failed",
+                            combination.getDescription()));
+
+            createSessionByConfigs(outputConfigs);
+
+            boolean constraintPresent = false;
+            List<Surface> constrainedOutputs = new ArrayList<>(outputSurfaces);
+
+            while (!outputSurfaces.isEmpty()) {
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                // Check to see how many outputs can be combined in to a single request including
+                // the first output surface and respecting the advertised constraints
+                Iterator<OutputConfiguration> it = outputConfigs.iterator();
+                OutputConfiguration config = it.next();
+                HashSet<Integer> currentProfiles = new HashSet<>();
+                currentProfiles.add(config.getDynamicRangeProfile());
+                requestBuilder.addTarget(config.getSurface());
+                outputSurfaces.remove(config.getSurface());
+                it.remove();
+                while (it.hasNext()) {
+                    config = it.next();
+                    Integer currentProfile = config.getDynamicRangeProfile();
+                    Set<Integer> newLimitations = profiles.getProfileCaptureRequestConstraints(
+                            currentProfile);
+                    if (newLimitations.isEmpty() || (newLimitations.containsAll(currentProfiles))) {
+                        currentProfiles.add(currentProfile);
+                        requestBuilder.addTarget(config.getSurface());
+                        outputSurfaces.remove(config.getSurface());
+                        it.remove();
+                    } else if (!constraintPresent && !newLimitations.isEmpty() &&
+                            !newLimitations.containsAll(currentProfiles)) {
+                        constraintPresent = true;
+                    }
+                }
+
+                CaptureRequest request = requestBuilder.build();
+                CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                        mock(CameraCaptureSession.CaptureCallback.class);
+                mCameraSession.capture(request, mockCaptureCallback, mHandler);
+                verify(mockCaptureCallback,
+                        timeout(TIMEOUT_FOR_RESULT_MS).atLeastOnce())
+                        .onCaptureCompleted(
+                                eq(mCameraSession),
+                                eq(request),
+                                isA(TotalCaptureResult.class));
+
+                verify(mockCaptureCallback, never()).
+                        onCaptureFailed(
+                                eq(mCameraSession),
+                                eq(request),
+                                isA(CaptureFailure.class));
+            }
+
+            if (constraintPresent) {
+                // Capture requests that include output surfaces with dynamic range profiles that
+                // cannot be combined must throw a corresponding exception
+                CaptureRequest.Builder requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                for (Surface s : constrainedOutputs) {
+                    requestBuilder.addTarget(s);
+                }
+
+                CaptureRequest request = requestBuilder.build();
+                CameraCaptureSession.CaptureCallback mockCaptureCallback =
+                        mock(CameraCaptureSession.CaptureCallback.class);
+                try {
+                    mCameraSession.capture(request, mockCaptureCallback, mHandler);
+                    fail("Capture request to outputs with incompatible dynamic range profiles "
+                            + "must always fail!");
+                } catch (IllegalArgumentException e) {
+                    // Expected
+                }
+            }
+
+            Log.i(TAG, String.format("Done with camera %s, combination: %s, closing session",
+                    cameraId, combination.getDescription()));
+        } catch (Throwable e) {
+            mCollector.addMessage(
+                    String.format("Closing down for combination: %s failed due to: %s",
+                            combination.getDescription(), e.getMessage()));
+        }
+
+        targets.close();
+    }
+
+    /**
      * Test for making sure the required reprocess input/output combinations for each hardware
      * level and capability work as expected.
      */
@@ -2693,32 +2891,34 @@
 
     private static Size getMaxPreviewSize(Context context, String cameraId) {
         try {
-            WindowManager windowManager =
-                (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-            Display display = windowManager.getDefaultDisplay();
+            WindowManager windowManager = context.getSystemService(WindowManager.class);
+            assertNotNull("Could not find WindowManager service.", windowManager);
 
-            int width = display.getWidth();
-            int height = display.getHeight();
+            WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
+            Rect windowBounds = windowMetrics.getBounds();
+
+            int width = windowBounds.width();
+            int height = windowBounds.height();
 
             if (height > width) {
                 height = width;
-                width = display.getHeight();
+                width = windowBounds.height();
             }
 
-            CameraManager camMgr =
-                (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
+            CameraManager camMgr = context.getSystemService(CameraManager.class);
             List<Size> orderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(
-                cameraId, camMgr, PREVIEW_SIZE_BOUND);
+                    cameraId, camMgr, PREVIEW_SIZE_BOUND);
 
             if (orderedPreviewSizes != null) {
                 for (Size size : orderedPreviewSizes) {
                     if (width >= size.getWidth() &&
-                        height >= size.getHeight())
+                            height >= size.getHeight()) {
                         return size;
+                    }
                 }
             }
         } catch (Exception e) {
-            Log.e(TAG, "getMaxPreviewSize Failed. "+e.toString());
+            Log.e(TAG, "getMaxPreviewSize Failed. " + e);
         }
         return PREVIEW_SIZE_BOUND;
     }
diff --git a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
index a588c3e..e4c6246 100644
--- a/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -433,6 +433,7 @@
             case REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING:
             case REQUEST_AVAILABLE_CAPABILITIES_LOGICAL_MULTI_CAMERA:
             case REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME:
+            case REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT:
                 // Tested in ExtendedCameraCharacteristicsTest
                 return;
             case REQUEST_AVAILABLE_CAPABILITIES_SECURE_IMAGE_DATA:
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index 5576780..966de25 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -16,10 +16,25 @@
 
 package android.hardware.camera2.cts.testcases;
 
-import static android.hardware.camera2.cts.CameraTestUtils.*;
+import static android.hardware.camera2.cts.CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.PREVIEW_SIZE_BOUND;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CLOSE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_CONFIGURE_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SESSION_READY_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.assertFalse;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNotNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertNull;
+import static android.hardware.camera2.cts.CameraTestUtils.assertTrue;
+import static android.hardware.camera2.cts.CameraTestUtils.checkSessionConfigurationSupported;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSession;
+import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSessionWithConfig;
+import static android.hardware.camera2.cts.CameraTestUtils.getPreviewSizeBound;
+import static android.hardware.camera2.cts.CameraTestUtils.getSupportedPreviewSizes;
+import static android.hardware.camera2.cts.CameraTestUtils.isSessionConfigSupported;
 
-import static com.android.ex.camera2.blocking.BlockingSessionCallback.*;
-import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_CLOSED;
+import static com.android.ex.camera2.blocking.BlockingSessionCallback.SESSION_READY;
+import static com.android.ex.camera2.blocking.BlockingStateCallback.STATE_CLOSED;
 
 import android.app.Activity;
 import android.content.Context;
@@ -28,13 +43,11 @@
 import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCaptureSession;
-import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
-import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback;
 import android.hardware.camera2.CameraDevice;
-import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.cts.Camera2MultiViewCtsActivity;
+import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.params.OutputConfiguration;
@@ -43,7 +56,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
-import android.os.SystemClock;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
@@ -58,9 +70,6 @@
 
 import junit.framework.Assert;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 
 import java.util.Arrays;
@@ -141,7 +150,8 @@
     protected void updatePreviewDisplayRotation(Size previewSize, TextureView textureView) {
         int rotationDegrees = 0;
         Camera2MultiViewCtsActivity activity = (Camera2MultiViewCtsActivity) mActivity;
-        int displayRotation = activity.getWindowManager().getDefaultDisplay().getRotation();
+
+        int displayRotation = activity.getDisplay().getRotation();
         Configuration config = activity.getResources().getConfiguration();
 
         // Get UI display rotation
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
index af15ff0..e4def35 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -16,7 +16,6 @@
 
 package android.hardware.camera2.cts;
 
-import androidx.annotation.NonNull;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageFormat;
@@ -25,45 +24,47 @@
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.MultiResolutionImageReader;
+import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.cts.helpers.CameraErrorCollector;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.TotalCaptureResult;
-import android.hardware.cts.helpers.CameraUtils;
-import android.hardware.camera2.params.MeteringRectangle;
-import android.hardware.camera2.params.MandatoryStreamCombination;
 import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
+import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.MultiResolutionStreamConfigurationMap;
 import android.hardware.camera2.params.MultiResolutionStreamInfo;
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.SessionConfiguration;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.cts.helpers.CameraUtils;
 import android.location.Location;
 import android.location.LocationManager;
 import android.media.ExifInterface;
 import android.media.Image;
+import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.media.ImageWriter;
-import android.media.Image.Plane;
 import android.os.Build;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.util.Log;
 import android.util.Pair;
-import android.util.Size;
 import android.util.Range;
-import android.view.Display;
+import android.util.Size;
 import android.view.Surface;
 import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.annotation.NonNull;
 
 import com.android.ex.camera2.blocking.BlockingCameraManager;
 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
@@ -79,6 +80,8 @@
 import java.io.IOException;
 import java.lang.reflect.Array;
 import java.nio.ByteBuffer;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -88,14 +91,13 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Random;
 import java.util.Set;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * A package private utility class for wrapping up the camera2 cts test common utility functions
@@ -139,6 +141,8 @@
 
     public static final String OFFLINE_CAMERA_ID = "offline_camera_id";
     public static final String REPORT_LOG_NAME = "CtsCameraTestCases";
+    public static final String MPC_REPORT_LOG_NAME = "MediaPerformanceClassLogs";
+    public static final String MPC_STREAM_NAME = "CameraCts";
 
     private static final int EXIF_DATETIME_LENGTH = 19;
     private static final int EXIF_DATETIME_ERROR_MARGIN_SEC = 60;
@@ -236,6 +240,8 @@
         public List<ImageReader> mRawTargets = new ArrayList<>();
         public List<ImageReader> mHeicTargets = new ArrayList<>();
         public List<ImageReader> mDepth16Targets = new ArrayList<>();
+        public List<ImageReader> mP010Targets = new ArrayList<>();
+
 
         public List<MultiResolutionImageReader> mPrivMultiResTargets = new ArrayList<>();
         public List<MultiResolutionImageReader> mJpegMultiResTargets = new ArrayList<>();
@@ -264,6 +270,9 @@
             for (ImageReader target : mDepth16Targets) {
                 target.close();
             }
+            for (ImageReader target : mP010Targets) {
+                target.close();
+            }
 
             for (MultiResolutionImageReader target : mPrivMultiResTargets) {
                 target.close();
@@ -284,7 +293,8 @@
             List<OutputConfiguration> outputConfigs, List<Surface> outputSurfaces,
             int format, Size targetSize, int numBuffers, String overridePhysicalCameraId,
             MultiResolutionStreamConfigurationMap multiResStreamConfig,
-            boolean createMultiResiStreamConfig, ImageDropperListener listener, Handler handler) {
+            boolean createMultiResiStreamConfig, ImageDropperListener listener, Handler handler,
+            int dynamicRangeProfile) {
         if (createMultiResiStreamConfig) {
             Collection<MultiResolutionStreamInfo> multiResolutionStreams =
                     multiResStreamConfig.getOutputInfo(format);
@@ -319,6 +329,7 @@
                 if (overridePhysicalCameraId != null) {
                     config.setPhysicalCameraId(overridePhysicalCameraId);
                 }
+                config.setDynamicRangeProfile(dynamicRangeProfile);
                 outputConfigs.add(config);
                 outputSurfaces.add(config.getSurface());
                 targets.mPrivTargets.add(target);
@@ -330,6 +341,7 @@
                 if (overridePhysicalCameraId != null) {
                     config.setPhysicalCameraId(overridePhysicalCameraId);
                 }
+                config.setDynamicRangeProfile(dynamicRangeProfile);
                 outputConfigs.add(config);
                 outputSurfaces.add(config.getSurface());
 
@@ -352,6 +364,9 @@
                     case ImageFormat.DEPTH16:
                       targets.mDepth16Targets.add(target);
                       break;
+                    case ImageFormat.YCBCR_P010:
+                      targets.mP010Targets.add(target);
+                      break;
                     default:
                       fail("Unknown/Unsupported output format " + format);
                 }
@@ -377,7 +392,29 @@
             List<Surface> outputSurfaces, List<Surface> uhSurfaces, int numBuffers,
             boolean substituteY8, boolean substituteHeic, String overridePhysicalCameraId,
             MultiResolutionStreamConfigurationMap multiResStreamConfig, Handler handler) {
+        setupConfigurationTargets(streamsInfo, targets, outputConfigs, outputSurfaces, uhSurfaces,
+                numBuffers, substituteY8, substituteHeic, overridePhysicalCameraId,
+                multiResStreamConfig, handler, /*dynamicRangeProfiles*/ null);
+    }
 
+    public static void setupConfigurationTargets(List<MandatoryStreamInformation> streamsInfo,
+            StreamCombinationTargets targets,
+            List<OutputConfiguration> outputConfigs,
+            List<Surface> outputSurfaces, List<Surface> uhSurfaces, int numBuffers,
+            boolean substituteY8, boolean substituteHeic, String overridePhysicalCameraId,
+            MultiResolutionStreamConfigurationMap multiResStreamConfig, Handler handler,
+            List<Integer> dynamicRangeProfiles) {
+
+        Random rnd = new Random();
+        // 10-bit output capable streams will use a fixed dynamic range profile in case
+        // dynamicRangeProfiles.size() == 1 or random in case dynamicRangeProfiles.size() > 1
+        boolean use10BitRandomProfile = (dynamicRangeProfiles != null) &&
+                (dynamicRangeProfiles.size() > 1);
+        if (use10BitRandomProfile) {
+            Long seed = rnd.nextLong();
+            Log.i(TAG, "Random seed used for selecting 10-bit output: " + seed);
+            rnd.setSeed(seed);
+        }
         ImageDropperListener imageDropperListener = new ImageDropperListener();
         List<Surface> chosenSurfaces;
         for (MandatoryStreamInformation streamInfo : streamsInfo) {
@@ -394,6 +431,19 @@
             } else if (substituteHeic && (format == ImageFormat.JPEG)) {
                 format = ImageFormat.HEIC;
             }
+
+            int dynamicRangeProfile = DynamicRangeProfiles.STANDARD;
+            if (streamInfo.is10BitCapable() && use10BitRandomProfile) {
+                boolean override10bit = rnd.nextBoolean();
+                if (!override10bit) {
+                    dynamicRangeProfile = dynamicRangeProfiles.get(rnd.nextInt(
+                            dynamicRangeProfiles.size()));
+                    format = streamInfo.get10BitFormat();
+                }
+            } else if (streamInfo.is10BitCapable() && (dynamicRangeProfiles != null)) {
+                dynamicRangeProfile = dynamicRangeProfiles.get(0);
+                format = streamInfo.get10BitFormat();
+            }
             Size[] availableSizes = new Size[streamInfo.getAvailableSizes().size()];
             availableSizes = streamInfo.getAvailableSizes().toArray(availableSizes);
             Size targetSize = CameraTestUtils.getMaxSize(availableSizes);
@@ -405,13 +455,15 @@
                 case ImageFormat.PRIVATE:
                 case ImageFormat.JPEG:
                 case ImageFormat.YUV_420_888:
+                case ImageFormat.YCBCR_P010:
                 case ImageFormat.Y8:
                 case ImageFormat.HEIC:
                 case ImageFormat.DEPTH16:
                 {
                     configureTarget(targets, outputConfigs, chosenSurfaces, format,
                             targetSize, numBuffers, overridePhysicalCameraId, multiResStreamConfig,
-                            createMultiResReader, imageDropperListener, handler);
+                            createMultiResReader, imageDropperListener, handler,
+                            dynamicRangeProfile);
                     break;
                 }
                 case ImageFormat.RAW_SENSOR: {
@@ -421,7 +473,7 @@
                         configureTarget(targets, outputConfigs, chosenSurfaces, format,
                                 targetSize, numBuffers, overridePhysicalCameraId,
                                 multiResStreamConfig, createMultiResReader, imageDropperListener,
-                                handler);
+                                handler, dynamicRangeProfile);
                     }
                     break;
                 }
@@ -3426,21 +3478,23 @@
     }
 
     public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
-        Display display = windowManager.getDefaultDisplay();
+        WindowMetrics windowMetrics = windowManager.getCurrentWindowMetrics();
+        Rect windowBounds = windowMetrics.getBounds();
 
-        int width = display.getWidth();
-        int height = display.getHeight();
+        int windowHeight = windowBounds.height();
+        int windowWidth = windowBounds.width();
 
-        if (height > width) {
-            height = width;
-            width = display.getHeight();
+        if (windowHeight > windowWidth) {
+            windowHeight = windowWidth;
+            windowWidth = windowBounds.height();
         }
 
-        if (bound.getWidth() <= width &&
-            bound.getHeight() <= height)
+        if (bound.getWidth() <= windowWidth
+                && bound.getHeight() <= windowHeight) {
             return bound;
-        else
-            return new Size(width, height);
+        } else {
+            return new Size(windowWidth, windowHeight);
+        }
     }
 
     /**
@@ -3740,8 +3794,10 @@
         return zoomRatios;
     }
 
-    private static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
-    private static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
+    public static final int PERFORMANCE_CLASS_NOT_MET = 0;
+    public static final int PERFORMANCE_CLASS_R = Build.VERSION_CODES.R;
+    public static final int PERFORMANCE_CLASS_S = Build.VERSION_CODES.R + 1;
+    public static final int PERFORMANCE_CLASS_CURRENT = PERFORMANCE_CLASS_S;
 
     /**
      * Check whether this mobile device is R performance class as defined in CDD
diff --git a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index 1930b33..5e47542 100644
--- a/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/camera/utils/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -24,8 +24,10 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.CameraTestUtils;
+import android.hardware.camera2.params.DynamicRangeProfiles;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.params.Capability;
+import android.util.ArraySet;
 import android.util.Range;
 import android.util.Size;
 import android.util.Log;
@@ -77,7 +79,7 @@
 
     // Last defined capability enum, for iterating over all of them
     public static final int LAST_CAPABILITY_ENUM =
-            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_REMOSAIC_REPROCESSING;
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT;
 
     // Access via getAeModeName() to account for vendor extensions
     public static final String[] AE_MODE_NAMES = new String[] {
@@ -792,6 +794,22 @@
     }
 
     /**
+     * Get and check the available dynamic range profiles.
+     *
+     * @return the available dynamic range profiles
+     */
+    public Set<Integer> getAvailableDynamicRangeProfilesChecked() {
+        DynamicRangeProfiles profiles = mCharacteristics.get(
+                CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES);
+
+        if (profiles == null) {
+            return new ArraySet<Integer>();
+        }
+
+        return profiles.getSupportedProfiles();
+    }
+
+    /**
      * Get and check the available tone map modes.
      *
      * @return the available tone map modes
@@ -1828,7 +1846,7 @@
                 modeList.contains(CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF));
         checkArrayValuesInRange(key, modes,
                 CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_OFF,
-                CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_ON);
+                CameraMetadata.CONTROL_VIDEO_STABILIZATION_MODE_PREVIEW_STABILIZATION);
 
         return modes;
     }
diff --git a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
index 5b4b485..df71087 100644
--- a/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
+++ b/tests/camera/utils/src/android/hardware/cts/helpers/CameraUtils.java
@@ -17,12 +17,14 @@
 package android.hardware.cts.helpers;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.devicestate.DeviceStateManager;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
@@ -30,8 +32,11 @@
 
 import androidx.test.InstrumentationRegistry;
 
+import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
 /**
@@ -273,4 +278,30 @@
         }
     }
 
+    /**
+     * Uses {@link DeviceStateManager} to determine if the device is foldable or not. It relies on
+     * the OEM exposing supported states, and setting
+     * com.android.internal.R.array.config_foldedDeviceStates correctly with the folded states.
+     *
+     * @return true is the device is a foldable; false otherwise
+     */
+    public static boolean isDeviceFoldable(Context mContext) {
+        DeviceStateManager deviceStateManager =
+                mContext.getSystemService(DeviceStateManager.class);
+        if (deviceStateManager == null) {
+            Log.w(TAG, "Couldn't locate DeviceStateManager to detect if the device is foldable"
+                    + " or not. Defaulting to not-foldable.");
+            return false;
+        }
+        Set<Integer> supportedStates = Arrays.stream(
+                deviceStateManager.getSupportedStates()).boxed().collect(Collectors.toSet());
+
+        Resources systemRes = Resources.getSystem();
+        int foldedStatesArrayIdentifier = systemRes.getIdentifier("config_foldedDeviceStates",
+                "array", "android");
+        int[] foldedDeviceStates = systemRes.getIntArray(foldedStatesArrayIdentifier);
+
+        // Device is a foldable if supportedStates contains any state in foldedDeviceStates
+        return Arrays.stream(foldedDeviceStates).anyMatch(supportedStates::contains);
+    }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
index 18d11a3..93e43c9 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/Assertions.java
@@ -309,30 +309,22 @@
     /**
      * This method is used to remove unexpected events if the test should only
      * contain session level events.
-     * In special case, there are some events, such as {@link #TYPE_VIEW_INSETS_CHANGED},
-     * will be accidentally generated by the system. Because these events were
-     * not expected in the test, remove them if needed.
+     * In special case, there are some events, such as {@link #TYPE_WINDOW_BOUNDS_CHANGED}
+     * and {@link #TYPE_VIEW_INSETS_CHANGED}, will be accidentally generated by
+     * the system. Because these events were not expected in the test, remove
+     * them if needed.
      */
     public static List<ContentCaptureEvent> removeUnexpectedEvents(
             @NonNull List<ContentCaptureEvent> events) {
         return Collections.unmodifiableList(events).stream().filter(
-                e -> e.getType() < TYPE_VIEW_INSETS_CHANGED
+                e -> e.getType() != TYPE_WINDOW_BOUNDS_CHANGED
+                        && e.getType() != TYPE_VIEW_INSETS_CHANGED
                         && e.getType() != TYPE_VIEW_TREE_APPEARING
                         && e.getType() != TYPE_VIEW_TREE_APPEARED
         ).collect(Collectors.toList());
     }
 
     /**
-     * Used to remove the unknown event
-     */
-    public static List<ContentCaptureEvent> removeUnknownEvent(
-            @NonNull List<ContentCaptureEvent> events) {
-        return Collections.unmodifiableList(events).stream().filter(
-                e -> e.getType() <= TYPE_VIEW_INSETS_CHANGED
-        ).collect(Collectors.toList());
-    }
-
-    /**
      * Asserts that a session for the given activity has events at all.
      */
     public static void assertNoEvents(@NonNull Session session,
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
index 7e454cf..7685053 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/CtsContentCaptureService.java
@@ -39,6 +39,7 @@
 import android.view.contentcapture.DataShareRequest;
 import android.view.contentcapture.ViewNode;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -72,6 +73,9 @@
 
     private static int sIdCounter;
 
+    private static Object sLock = new Object();
+
+    @GuardedBy("sLock")
     private static ServiceWatcher sServiceWatcher;
 
     private final int mId = ++sIdCounter;
@@ -158,11 +162,13 @@
 
     @NonNull
     public static ServiceWatcher setServiceWatcher() {
-        if (sServiceWatcher != null) {
-            throw new IllegalStateException("There Can Be Only One!");
+        synchronized (sLock) {
+            if (sServiceWatcher != null) {
+                throw new IllegalStateException("There Can Be Only One!");
+            }
+            sServiceWatcher = new ServiceWatcher();
+            return sServiceWatcher;
         }
-        sServiceWatcher = new ServiceWatcher();
-        return sServiceWatcher;
     }
 
     public static void resetStaticState() {
@@ -173,20 +179,29 @@
         // TODO(b/123540602): each test should use a different service instance, but we need
         // to provide onConnected() / onDisconnected() methods first and then change the infra so
         // we can wait for those
+        synchronized (sLock) {
+            if (sServiceWatcher != null) {
+                Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
+                sServiceWatcher = null;
+            }
+        }
+    }
 
-        if (sServiceWatcher != null) {
-            Log.wtf(TAG, "resetStaticState(): should not have sServiceWatcher");
-            sServiceWatcher = null;
+    private static ServiceWatcher getServiceWatcher() {
+        synchronized (sLock) {
+            return sServiceWatcher;
         }
     }
 
     public static void clearServiceWatcher() {
-        if (sServiceWatcher != null) {
-            if (sServiceWatcher.mReadyToClear) {
-                sServiceWatcher.mService = null;
-                sServiceWatcher = null;
-            } else {
-                sServiceWatcher.mReadyToClear = true;
+        synchronized (sLock) {
+            if (sServiceWatcher != null) {
+                if (sServiceWatcher.mReadyToClear) {
+                    sServiceWatcher.mService = null;
+                    sServiceWatcher = null;
+                } else {
+                    sServiceWatcher.mReadyToClear = true;
+                }
             }
         }
     }
@@ -204,21 +219,22 @@
 
     @Override
     public void onConnected() {
-        Log.i(TAG, "onConnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+        final ServiceWatcher sw = getServiceWatcher();
+        Log.i(TAG, "onConnected(id=" + mId + "): sServiceWatcher=" + sw);
 
-        if (sServiceWatcher == null) {
+        if (sw == null) {
             addException("onConnected() without a watcher");
             return;
         }
 
-        if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
-            addException("onConnected(): already created: %s", sServiceWatcher);
+        if (!sw.mReadyToClear && sw.mService != null) {
+            addException("onConnected(): already created: %s", sw);
             return;
         }
 
-        sServiceWatcher.mService = this;
-        sServiceWatcher.mCreated.countDown();
-        sServiceWatcher.mReadyToClear = false;
+        sw.mService = this;
+        sw.mCreated.countDown();
+        sw.mReadyToClear = false;
 
         if (mConnectedLatch.getCount() == 0) {
             addException("already connected: %s", mConnectedLatch);
@@ -228,19 +244,20 @@
 
     @Override
     public void onDisconnected() {
-        Log.i(TAG, "onDisconnected(id=" + mId + "): sServiceWatcher=" + sServiceWatcher);
+        final ServiceWatcher sw = getServiceWatcher();
+        Log.i(TAG, "onDisconnected(id=" + mId + "): sServiceWatcher=" + sw);
 
         if (mDisconnectedLatch.getCount() == 0) {
             addException("already disconnected: %s", mConnectedLatch);
         }
         mDisconnectedLatch.countDown();
 
-        if (sServiceWatcher == null) {
+        if (sw == null) {
             addException("onDisconnected() without a watcher");
             return;
         }
-        if (sServiceWatcher.mService == null) {
-            addException("onDisconnected(): no service on %s", sServiceWatcher);
+        if (sw.mService == null) {
+            addException("onDisconnected(): no service on %s", sw);
             return;
         }
         // Notify test case as well
@@ -249,7 +266,7 @@
             mOnDisconnectListener = null;
             latch.countDown();
         }
-        sServiceWatcher.mDestroyed.countDown();
+        sw.mDestroyed.countDown();
         clearServiceWatcher();
     }
 
@@ -449,7 +466,7 @@
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         super.dump(fd, pw, args);
 
-        pw.print("sServiceWatcher: "); pw.println(sServiceWatcher);
+        pw.print("sServiceWatcher: "); pw.println(getServiceWatcher());
         pw.print("sExceptions: "); pw.println(sExceptions);
         pw.print("sIdCounter: "); pw.println(sIdCounter);
         pw.print("mId: "); pw.println(mId);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
index d1f0c6a..3a1ada3 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingService.java
@@ -24,6 +24,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.DataShareRequest;
 import android.view.contentcapture.DataShareWriteAdapter;
@@ -73,6 +74,8 @@
                     public void onWrite(ParcelFileDescriptor destination) {
                         if (mShouldAttemptConcurrentRequest) {
                             attemptConcurrentRequest();
+                            // Waiting for the request to arrive at the server
+                            SystemClock.sleep(500);
                         }
                         try (OutputStream outputStream =
                                      new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
index 421a5bd..0e71cac 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/DataSharingServiceTest.java
@@ -141,9 +141,9 @@
         CtsContentCaptureService ccService = enableService();
 
         ccService.setDataSharingEnabled(true);
-        sKillingStage = KillingStage.BEFORE_WRITE;
-        getApplicationContext().startService(
-                new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+        Intent intent = new Intent(getApplicationContext(), OutOfProcessDataSharingService.class);
+        intent.putExtra("KillingStage", KillingStage.BEFORE_WRITE.name());
+        getApplicationContext().startService(intent);
 
         PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
 
@@ -156,13 +156,12 @@
         CtsContentCaptureService ccService = enableService();
 
         ccService.setDataSharingEnabled(true);
-        sKillingStage = KillingStage.DURING_WRITE;
-        getApplicationContext().startService(
-                new Intent(getApplicationContext(), OutOfProcessDataSharingService.class));
+        Intent intent = new Intent(getApplicationContext(), OutOfProcessDataSharingService.class);
+        intent.putExtra("KillingStage", KillingStage.DURING_WRITE.name());
+        getApplicationContext().startService(intent);
 
-        PollingCheck.waitFor(() -> ccService.mDataShareSessionErrorCode > 0);
+        PollingCheck.waitFor(() -> ccService.mDataShareSessionFinished);
 
-        assertThat(ccService.mDataShareSessionErrorCode).isEqualTo(
-                ContentCaptureManager.DATA_SHARE_ERROR_UNKNOWN);
+        assertThat(ccService.mDataShareSessionSucceeded).isTrue();
     }
 }
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
index d00b031..6f6d457 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/EventsAssertor.java
@@ -102,6 +102,16 @@
     }
 
     /**
+     * Asserts the basic contents of a {@link ContentCaptureEvent#TYPE_CONTEXT_UPDATED} event.
+     */
+    public EventsAssertor assertContextUpdated() {
+        assertNextEvent((event) -> assertSessionLevelEvent(event),
+                ContentCaptureEvent.TYPE_CONTEXT_UPDATED,
+                "no TYPE_CONTEXT_UPDATED event");
+        return this;
+    }
+
+    /**
      * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_APPEARED}
      * event for a decor view.
      *
@@ -223,6 +233,27 @@
         return assertViewDisappeared(session.newAutofillId(parentId, childId));
     }
 
+    /**
+     * Asserts the contents of a {@link ContentCaptureEvent#TYPE_VIEW_TEXT_CHANGED} event.
+     */
+    @NonNull
+    public EventsAssertor assertViewTextChanged(AutofillId expectedId, String expectedText) {
+        assertNextEvent((event) -> assertTextChangedEvent(event, expectedId, expectedText),
+                ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED,
+                String.format("no TYPE_VIEW_TEXT_CHANGED event for %s:%s",
+                        expectedId, expectedText));
+        return this;
+    }
+
+    private String assertTextChangedEvent(ContentCaptureEvent event, AutofillId expectedId,
+            String expectedText) {
+        assertWithMessage("Wrong id on %s", event).that(event.getId())
+                .isEqualTo(expectedId);
+        assertWithMessage("Wrong text on %s", event).that(event.getText().toString())
+                .isEqualTo(expectedText);
+        return null;
+    }
+
     @Nullable
     private String assertVirtualViewEvent(@NonNull ContentCaptureEvent event,
             @NonNull AutofillId expectedId, @Nullable String expectedText) {
@@ -260,7 +291,7 @@
         assertWithMessage("no autofillIds on event %s", event).that(ids)
                 .isNotNull();
         assertWithMessage("wrong autofillId on event %s", event)
-                .that(ids).containsExactly((Object[]) expectedIds).inOrder();
+                .that(ids).containsExactly((Object[]) expectedIds);
         assertWithMessage("event %s should not have autofillId", event)
                 .that(event.getId()).isNull();
         return null;
@@ -383,6 +414,10 @@
                 + mEvents.size() + "): " + mEvents);
     }
 
+    public ContentCaptureEvent getLastEvent() {
+        return mEvents.get(mNextEvent - 1);
+    }
+
     private interface EventAssertion {
         @Nullable
         String getErrorMessage(@NonNull ContentCaptureEvent event);
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
index 0857828..8b86214 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivity.java
@@ -23,7 +23,6 @@
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
-import static android.contentcaptureservice.cts.Assertions.removeUnknownEvent;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -97,17 +96,11 @@
     @NonNull
     public List<ContentCaptureEvent> assertJustInitialViewsAppeared(@NonNull Session session,
             int additionalEvents) {
+        assertBaseInformation(session);
+
         final LoginActivity activity = this;
         final ContentCaptureSessionId sessionId = session.id;
-        assertRightActivity(session, sessionId, activity);
-
-        // Sanity check
-        assertSessionId(sessionId, activity.mUsernameLabel);
-        assertSessionId(sessionId, activity.mUsername);
-        assertSessionId(sessionId, activity.mPassword);
-        assertSessionId(sessionId, activity.mPasswordLabel);
-
-        final List<ContentCaptureEvent> events = removeUnknownEvent(session.getEvents());
+        final List<ContentCaptureEvent> events = session.getEvents();
         Log.v(TAG, "events(" + events.size() + "): " + events);
         // TODO(b/123540067): ideally it should be X so it reflects just the views defined
         // in the layout - right now it's generating events for 2 intermediate parents
@@ -139,6 +132,52 @@
     }
 
     /**
+     * Asserts the events generated when this activity was launched, up to the
+     * {@code TYPE_INITIAL_VIEW_HIERARCHY_FINISHED} event.
+     */
+    @NonNull
+    public EventsAssertor assertInitialViewsAppeared(@NonNull Session session) {
+        assertBaseInformation(session);
+
+        final LoginActivity activity = this;
+        final ContentCaptureSessionId sessionId = session.id;
+        final List<ContentCaptureEvent> events = session.getEvents();
+        Log.v(TAG, "events(" + events.size() + "): " + events);
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+        final View decorView = activity.getDecorView();
+        final View rootView = activity.getRootView();
+
+        EventsAssertor assertor = new EventsAssertor(events);
+        assertor.isAtLeast(MIN_EVENTS)
+                .assertSessionResumed()
+                .assertViewTreeStarted()
+                .assertDecorViewAppeared(decorView)
+                .assertViewAppeared(grandpa2, decorView.getAutofillId())
+                .assertViewAppeared(grandpa1, grandpa2.getAutofillId())
+                .assertViewAppeared(sessionId, rootView, grandpa1.getAutofillId())
+                .assertViewAppeared(sessionId, activity.mUsernameLabel, rootId)
+                .assertViewAppeared(sessionId, activity.mUsername, rootId)
+                .assertViewAppeared(sessionId, activity.mPasswordLabel, rootId)
+                .assertViewAppeared(sessionId, activity.mPassword, rootId)
+                .assertViewTreeFinished();
+
+        return assertor;
+    }
+
+    private void assertBaseInformation(@NonNull Session session) {
+        final LoginActivity activity = this;
+        final ContentCaptureSessionId sessionId = session.id;
+        assertRightActivity(session, sessionId, activity);
+
+        assertSessionId(sessionId, activity.mUsernameLabel);
+        assertSessionId(sessionId, activity.mUsername);
+        assertSessionId(sessionId, activity.mPassword);
+        assertSessionId(sessionId, activity.mPasswordLabel);
+    }
+
+    /**
      * Asserts the initial views disappeared after the activity was finished.
      */
     public void assertInitialViewsDisappeared(@NonNull List<ContentCaptureEvent> events,
@@ -180,6 +219,25 @@
         assertViewTreeFinished(events, i + 2);
     }
 
+    /**
+     * Asserts the initial views disappeared after the activity was finished.
+     */
+    public void assertInitialViewsDisappeared(@NonNull EventsAssertor assertor) {
+        final LoginActivity activity = this;
+        final AutofillId rootId = activity.getRootView().getAutofillId();
+        final View decorView = activity.getDecorView();
+        final View grandpa1 = activity.getGrandParent();
+        final View grandpa2 = activity.getGrandGrandParent();
+
+        assertor.assertViewTreeStarted()
+                .assertViewDisappeared(
+                        rootId, grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                        decorView.getAutofillId(),
+                        activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                        activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId())
+                .assertViewTreeFinished();
+    }
+
     @Override
     protected void onResume() {
         super.onResume();
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
index acd5dc8..5a67ac0 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/LoginActivityTest.java
@@ -26,7 +26,6 @@
 import static android.contentcaptureservice.cts.Assertions.assertSessionPaused;
 import static android.contentcaptureservice.cts.Assertions.assertSessionResumed;
 import static android.contentcaptureservice.cts.Assertions.assertViewAppeared;
-import static android.contentcaptureservice.cts.Assertions.assertViewTextChanged;
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeFinished;
 import static android.contentcaptureservice.cts.Assertions.assertViewTreeStarted;
 import static android.contentcaptureservice.cts.Assertions.assertViewsOptionallyDisappeared;
@@ -314,16 +313,18 @@
         final Session session = service.getOnlyFinishedSession();
         Log.v(TAG, "session id: " + session.id);
 
-        final int additionalEvents = 2;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final ContentCaptureEvent event1 = assertContextUpdated(events, LoginActivity.MIN_EVENTS);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2)
+                .assertContextUpdated();
+
+        final ContentCaptureEvent event1 = assertor.getLastEvent();
         final ContentCaptureContext actualContext = event1.getContentCaptureContext();
         assertContentCaptureContext(actualContext);
 
-        final ContentCaptureEvent event2 = assertContextUpdated(events,
-                LoginActivity.MIN_EVENTS + 1);
+        assertor.assertContextUpdated();
+
+        final ContentCaptureEvent event2 = assertor.getLastEvent();
         assertThat(event2.getContentCaptureContext()).isNull();
     }
 
@@ -407,20 +408,14 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 2;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 2)
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "USER")
+                .assertViewTextChanged(activity.mPassword.getAutofillId(), "PASS");
 
-        final int i = LoginActivity.MIN_EVENTS;
-
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "USER");
-        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "PASS");
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     @Test
@@ -454,29 +449,26 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 8;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        final AutofillId usernameId = activity.mUsername.getAutofillId();
+        final AutofillId passwordId = activity.mPassword.getAutofillId();
 
-        final int i = LoginActivity.MIN_EVENTS;
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 8)
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "a")
+                .assertViewTextChanged(usernameId, "ab")
+                .assertViewTextChanged(usernameId, "")
+                .assertViewTextChanged(usernameId, "abc")
+                .assertViewTextChanged(passwordId, "d")
+                .assertViewTextChanged(passwordId, "")
+                .assertViewTextChanged(passwordId, "")
+                .assertViewTextChanged(passwordId, "de")
+                .assertViewTextChanged(passwordId, "def")
+                .assertViewTextChanged(passwordId, "")
+                .assertViewTextChanged(usernameId, "abc");
 
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "a");
-        assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "ab");
-        assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "");
-        assertViewTextChanged(events, i + 3, activity.mUsername.getAutofillId(), "abc");
-        assertViewTextChanged(events, i + 4, activity.mPassword.getAutofillId(), "d");
-        assertViewTextChanged(events, i + 5, activity.mPassword.getAutofillId(), "");
-        assertViewTextChanged(events, i + 6, activity.mPassword.getAutofillId(), "");
-        assertViewTextChanged(events, i + 7, activity.mPassword.getAutofillId(), "de");
-        assertViewTextChanged(events, i + 8, activity.mPassword.getAutofillId(), "def");
-        assertViewTextChanged(events, i + 9, activity.mPassword.getAutofillId(), "");
-        assertViewTextChanged(events, i + 10, activity.mUsername.getAutofillId(), "abc");
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     @Test
@@ -505,19 +497,13 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 5;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
 
-        final int i = LoginActivity.MIN_EVENTS;
-
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Android");
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     @Test
@@ -550,26 +536,22 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 4;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 4)
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good");
+        assertComposingSpan(assertor.getLastEvent().getText(), 0, 4);
 
-        final int i = LoginActivity.MIN_EVENTS;
+        assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good ");
+        assertNoComposingSpan(assertor.getLastEvent().getText());
 
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Good");
-        assertComposingSpan(events.get(i).getText(), 0, 4);
-        assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "Good ");
-        assertNoComposingSpan(events.get(i + 1).getText());
-        assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "Good morning");
+        assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning");
         // TODO: Change how the appending works to more realistically test the case where only
         // "morning" is in the composing state.
-        assertComposingSpan(events.get(i + 2).getText(), 0, 12);
+        assertComposingSpan(assertor.getLastEvent().getText(), 0, 12);
 
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     @Test
@@ -597,20 +579,14 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 3;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3)
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "Good morning")
+                .assertViewTextChanged(activity.mPassword.getAutofillId(), "How are you");
 
-        final int i = LoginActivity.MIN_EVENTS;
-
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Good morning");
-        assertViewTextChanged(events, i + 1, activity.mPassword.getAutofillId(), "How are you");
-
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     @Test
@@ -642,27 +618,24 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        final ContentCaptureSessionId sessionId = session.id;
 
-        assertRightActivity(session, sessionId, activity);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        final int additionalEvents = 5;
-        final List<ContentCaptureEvent> events = activity.assertInitialViewsAppeared(session,
-                additionalEvents);
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+                // TODO: The first two events should probably be merged.
+                .assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+        assertNoComposingSpan(assertor.getLastEvent().getText());
 
-        final int i = LoginActivity.MIN_EVENTS;
+        assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+        assertComposingSpan(assertor.getLastEvent().getText(), 1, 3);
 
-        // TODO: The first two events should probably be merged.
-        assertViewTextChanged(events, i, activity.mUsername.getAutofillId(), "Android");
-        assertNoComposingSpan(events.get(i).getText());
-        assertViewTextChanged(events, i + 1, activity.mUsername.getAutofillId(), "Android");
-        assertComposingSpan(events.get(i + 1).getText(), 1, 3);
-        assertViewTextChanged(events, i + 2, activity.mUsername.getAutofillId(), "Android");
-        assertNoComposingSpan(events.get(i + 2).getText());
-        assertViewTextChanged(events, i + 3, activity.mUsername.getAutofillId(), "end");
-        assertNoComposingSpan(events.get(i + 3).getText());
+        assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "Android");
+        assertNoComposingSpan(assertor.getLastEvent().getText());
 
-        activity.assertInitialViewsDisappeared(events, additionalEvents);
+        assertor.assertViewTextChanged(activity.mUsername.getAutofillId(), "end");
+        assertNoComposingSpan(assertor.getLastEvent().getText());
+
+        activity.assertInitialViewsDisappeared(assertor);
     }
 
     private void appendText(EditText editText, String text) {
@@ -920,20 +893,16 @@
         watcher.waitFor(DESTROYED);
 
         final Session session = service.getOnlyFinishedSession();
-        Log.v(TAG, "session id: " + session.id);
-
         final ContentCaptureSessionId sessionId = session.id;
-        assertRightActivity(session, sessionId, activity);
-
-        final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
-                /* additionalEvents= */ 2);
+        Log.v(TAG, "session id: " + sessionId);
         final AutofillId rootId = activity.getRootView().getAutofillId();
 
-        int i = LoginActivity.MIN_EVENTS - 1;
-        assertViewTreeFinished(events, i);
-        assertViewTreeStarted(events, i + 1);
-        assertViewAppeared(events, i + 2, sessionId, child, rootId);
-        assertViewTreeFinished(events, i + 3);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
+
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 3)
+                .assertViewTreeStarted()
+                .assertViewAppeared(sessionId, child, rootId)
+                .assertViewTreeFinished();
     }
 
     @Test
@@ -963,49 +932,26 @@
 
         final Session session = service.getOnlyFinishedSession();
         Log.v(TAG, "session id: " + session.id);
-
         final ContentCaptureSessionId sessionId = session.id;
-        assertRightActivity(session, sessionId, activity);
-        final int additionalEvents = 2; // 2 children views
-        final List<ContentCaptureEvent> events = activity.assertJustInitialViewsAppeared(session,
-                additionalEvents);
-        assertThat(events.size()).isAtLeast(LoginActivity.MIN_EVENTS + 5);
         final View decorView = activity.getDecorView();
         final View grandpa1 = activity.getGrandParent();
         final View grandpa2 = activity.getGrandGrandParent();
         final AutofillId rootId = activity.getRootView().getAutofillId();
-        int i = LoginActivity.MIN_EVENTS - 1;
 
-        assertViewTreeFinished(events, i);
-        assertViewTreeStarted(events, i + 1);
-        assertViewAppeared(events, i + 2, sessionId, children[0], rootId);
-        assertViewAppeared(events, i + 3, sessionId, children[1], rootId);
-        assertViewTreeFinished(events, i + 4);
+        final EventsAssertor assertor = activity.assertInitialViewsAppeared(session);
 
-        // TODO(b/122315042): assert parents disappeared
-        if (true) return;
-
-        // TODO(b/122315042): sometimes we get decor view disappareared events, sometimes we don't
-        // As we don't really care about those, let's fix it!
-        try {
-            assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
-                    rootId,
-                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
-                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
-                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
-                    children[0].getAutofillId(), children[1].getAutofillId());
-        } catch (AssertionError e) {
-            Log.e(TAG, "Hack-ignoring assertion without decor view: " + e);
-            // Try again removing it...
-            assertViewsOptionallyDisappeared(events, LoginActivity.MIN_EVENTS + additionalEvents,
-                    rootId,
-                    grandpa1.getAutofillId(), grandpa2.getAutofillId(),
-                    decorView.getAutofillId(),
-                    activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
-                    activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
-                    children[0].getAutofillId(), children[1].getAutofillId());
-
-        }
+        assertor.isAtLeast(LoginActivity.MIN_EVENTS + 5)
+                .assertViewTreeStarted()
+                .assertViewAppeared(sessionId, children[0], rootId)
+                .assertViewAppeared(sessionId, children[1], rootId)
+                .assertViewTreeFinished()
+                .assertViewDisappeared(
+                        decorView.getAutofillId(),
+                        grandpa1.getAutofillId(), grandpa2.getAutofillId(),
+                        rootId,
+                        activity.mUsernameLabel.getAutofillId(), activity.mUsername.getAutofillId(),
+                        activity.mPasswordLabel.getAutofillId(), activity.mPassword.getAutofillId(),
+                        children[0].getAutofillId(), children[1].getAutofillId());
     }
 
     @Test
diff --git a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
index 27224c8..86a8b02 100644
--- a/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
+++ b/tests/contentcaptureservice/src/android/contentcaptureservice/cts/OutOfProcessDataSharingService.java
@@ -24,6 +24,7 @@
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.text.TextUtils;
 import android.view.contentcapture.ContentCaptureManager;
 import android.view.contentcapture.DataShareRequest;
 import android.view.contentcapture.DataShareWriteAdapter;
@@ -47,6 +48,7 @@
     byte[] mDataWritten = new byte[10_000];
     String mLocusId = "DataShare_CTSTest";
     String mMimeType = "application/octet-stream";
+    DataSharingServiceTest.KillingStage mKillingStage = DataSharingServiceTest.KillingStage.NONE;
 
     private final IBinder mBinder = new IOutOfProcessDataSharingService.Stub() {
         @Override
@@ -59,6 +61,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        setupKillingStage(intent);
         shareData();
         return START_NOT_STICKY;
     }
@@ -69,6 +72,14 @@
         return mBinder;
     }
 
+    private void setupKillingStage(Intent intent) {
+        String killingStage = intent.getStringExtra("KillingStage");
+        if (TextUtils.isEmpty(killingStage)) {
+            return;
+        }
+        mKillingStage = DataSharingServiceTest.KillingStage.valueOf(killingStage);
+    }
+
     private void shareData() {
         ContentCaptureManager manager =
                 getApplicationContext().getSystemService(ContentCaptureManager.class);
@@ -83,14 +94,14 @@
                     public void onWrite(ParcelFileDescriptor destination) {
                         try (OutputStream outputStream =
                                      new ParcelFileDescriptor.AutoCloseOutputStream(destination)) {
-                            if (DataSharingServiceTest.sKillingStage
+                            if (mKillingStage
                                     == DataSharingServiceTest.KillingStage.BEFORE_WRITE) {
                                 Process.killProcess(Process.myPid());
                                 return;
                             }
                             sRandom.nextBytes(mDataWritten);
                             outputStream.write(mDataWritten);
-                            if (DataSharingServiceTest.sKillingStage
+                            if (mKillingStage
                                     == DataSharingServiceTest.KillingStage.DURING_WRITE) {
                                 Process.killProcess(Process.myPid());
                                 return;
diff --git a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
index f0f22ae..ab586b2 100644
--- a/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlBuilderTest.java
@@ -81,7 +81,7 @@
                 .setCustomColor(mColorStateList)
                 .build();
 
-        assertControl(control, true);
+        assertControl(control, true /* isStateless */, true /* authRequired */);
     }
 
     @Test
@@ -98,13 +98,14 @@
                 .setStatusText(STATUS_TEXT_STATEFUL)
                 .setCustomIcon(mIcon)
                 .setCustomColor(mColorStateList)
+                .setAuthRequired(false)
                 .build();
 
         Control updatedControl = new Control.StatefulBuilder(control).build();
-        assertControl(updatedControl, false);
+        assertControl(updatedControl, false /* isStateless */, false /* authRequired */);
     }
 
-    private void assertControl(Control control, boolean isStateless) {
+    private void assertControl(Control control, boolean isStateless, boolean authRequired) {
         assertEquals(control.getTitle(), TITLE);
         assertEquals(control.getSubtitle(), SUBTITLE);
         assertEquals(control.getStructure(), STRUCTURE);
@@ -116,5 +117,6 @@
         assertEquals(control.getControlId(), CONTROL_ID2);
         assertEquals(control.getCustomColor(), mColorStateList);
         assertEquals(control.getCustomIcon(), mIcon);
+        assertEquals(control.isAuthRequired(), authRequired);
     }
 }
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
index 301c34e..36cfdd8 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsService.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -96,6 +96,7 @@
             .setDeviceType(DeviceTypes.TYPE_LIGHT)
             .setStructure("Home")
             .setControlTemplate(template)
+            .setAuthRequired(false)
             .build();
     }
 
@@ -351,6 +352,7 @@
             .setCustomIcon(c.getCustomIcon())
             .setCustomColor(c.getCustomColor())
             .setStatus(c.getStatus())
-            .setStatusText(c.getStatusText());
+            .setStatusText(c.getStatusText())
+            .setAuthRequired(c.isAuthRequired());
     }
 }
diff --git a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
index 61ccef3..7d5fb2a 100644
--- a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
+++ b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
@@ -371,6 +371,7 @@
         assertEquals(c1.getControlId(), c2.getControlId());
         assertEquals(c1.getCustomIcon(), c2.getCustomIcon());
         assertEquals(c1.getCustomColor(), c2.getCustomColor());
+        assertEquals(c1.isAuthRequired(), c2.isAuthRequired());
 
         assertTemplateEquals(c1.getControlTemplate(), c2.getControlTemplate());
     }
diff --git a/tests/devicepolicy/OWNERS b/tests/devicepolicy/OWNERS
index cf88726..19b6194 100644
--- a/tests/devicepolicy/OWNERS
+++ b/tests/devicepolicy/OWNERS
@@ -1,2 +1,2 @@
 # Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
-file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
\ No newline at end of file
+file:platform/frameworks/base:/core/java/android/app/admin/EnterprisePlatform_OWNERS
diff --git a/tests/devicepolicy/res/drawable/test_drawable_1.xml b/tests/devicepolicy/res/drawable/test_drawable_1.xml
new file mode 100644
index 0000000..8a20885
--- /dev/null
+++ b/tests/devicepolicy/res/drawable/test_drawable_1.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FFF44336"
+        android:pathData="M11,15h2v2h-2v-2zM11,7h2v6h-2L11,7zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/tests/devicepolicy/res/drawable/test_drawable_2.xml b/tests/devicepolicy/res/drawable/test_drawable_2.xml
new file mode 100644
index 0000000..98569a3
--- /dev/null
+++ b/tests/devicepolicy/res/drawable/test_drawable_2.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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#FF000000"
+        android:pathData="M11,15h2v2h-2v-2zM11,7h2v6h-2L11,7zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
+</vector>
diff --git a/tests/devicepolicy/res/values/strings.xml b/tests/devicepolicy/res/values/strings.xml
new file mode 100644
index 0000000..47ddabc
--- /dev/null
+++ b/tests/devicepolicy/res/values/strings.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ 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.
+  -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="test_string">test string</string>
+</resources>
\ No newline at end of file
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
index 9c5938e..1b1eb9c 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/AccountManagementTest.java
@@ -16,6 +16,8 @@
 
 package android.devicepolicy.cts;
 
+import static android.os.UserManager.DISALLOW_MODIFY_ACCOUNTS;
+
 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
 import static com.android.queryable.queries.ServiceQuery.service;
 
@@ -25,7 +27,6 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
-import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
 import android.app.admin.RemoteDevicePolicyManager;
 import android.content.ComponentName;
@@ -49,13 +50,10 @@
 
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
-
 @RunWith(BedsteadJUnit4.class)
 public final class AccountManagementTest {
     @ClassRule
@@ -158,78 +156,64 @@
         assertThat(mDpm.getAccountTypesWithManagementDisabled()).isEmpty();
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_fromDpcWithAccountManagementDisabled_accountAdded()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
 
-            // assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
-            //        .isEqualTo(EXISTING_ACCOUNT_TYPE);
+            assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+                    .isEqualTo(EXISTING_ACCOUNT_TYPE);
         } finally {
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
-            // TODO(b/197491427): AccountManager support in TestApp
-            // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
         }
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_fromDpcWithDisallowModifyAccountsRestriction_accountAdded()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
+            mDpm.addUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // Bundle result = addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            Bundle result = addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
 
-            //assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
-            //        .isEqualTo(EXISTING_ACCOUNT_TYPE);
+            assertThat(result.getString(AccountManager.KEY_ACCOUNT_TYPE))
+                    .isEqualTo(EXISTING_ACCOUNT_TYPE);
         } finally {
-            mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
-            // TODO(b/197491427): AccountManager support in TestApp
-            // removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+            mDpm.clearUserRestriction(mAdmin, DISALLOW_MODIFY_ACCOUNTS);
         }
     }
 
-    @Ignore("b/197491427")
     @Test
     @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_fromDpcWithDisallowModifyAccountsRestriction_accountRemoved()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
             // Management is disabled, but the DO/PO is still allowed to use the APIs
-            // TODO(b/197491427): AccountManager support in TestApp
-            // Do the following steps on the TestApp side:
-            // addAccountWithType(EXISTING_ACCOUNT_TYPE);
-            // Bundle result = removeAccount(ACCOUNT_WITH_EXISTING_TYPE);
+            addAccountWithType(sDeviceState.dpc(), EXISTING_ACCOUNT_TYPE);
+            Bundle result = removeAccount(sDeviceState.dpc(), ACCOUNT_WITH_EXISTING_TYPE);
 
-            // assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
+            assertThat(result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)).isTrue();
         } finally {
             mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
         }
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
-    public void addAccount_withDisallowModifyAccountsRestriction_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException {
+    public void addAccount_withDisallowModifyAccountsRestriction_throwsException() {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
@@ -241,17 +225,16 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_withDisallowModifyAccountsRestriction_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException,
-            InterruptedException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
             mDpm.addUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
 
             assertThrows(OperationCanceledException.class, () ->
-                    removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+                    removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
         } finally {
             // Account is automatically removed when the test app is removed
             mDpm.clearUserRestriction(mAdmin, UserManager.DISALLOW_MODIFY_ACCOUNTS);
@@ -259,7 +242,7 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void addAccount_withAccountManagementDisabled_throwsException() {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
@@ -273,17 +256,16 @@
     }
 
     @Test
-    @Postsubmit(reason = "new test with sleep")
+    @Postsubmit(reason = "new test")
     @CanSetPolicyTest(policy = AccountManagement.class)
     public void removeAccount_withAccountManagementDisabled_throwsException()
-            throws OperationCanceledException, AuthenticatorException, IOException,
-            InterruptedException {
+            throws Exception {
         try (TestAppInstance accountAuthenticatorApp = sAccountManagementApp.install()) {
-            addAccountWithType(EXISTING_ACCOUNT_TYPE);
+            addAccountFromInstrumentedAppWithType(EXISTING_ACCOUNT_TYPE);
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ true);
 
             assertThrows(OperationCanceledException.class, () ->
-                    removeAccount(ACCOUNT_WITH_EXISTING_TYPE));
+                    removeAccountFromInstrumentedApp(ACCOUNT_WITH_EXISTING_TYPE));
         } finally {
             // Account is automatically removed when the test app is removed
             mDpm.setAccountManagementDisabled(mAdmin, EXISTING_ACCOUNT_TYPE, /* disabled= */ false);
@@ -293,14 +275,25 @@
     /**
      * Blocks until an account of {@code type} is added.
      */
-    // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
-    private Bundle addAccountWithType(String type) {
+    // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+    private Bundle addAccountFromInstrumentedAppWithType(String type) {
         return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(type))
                 .toNotBeNull()
                 .errorOnFail()
                 .await();
     }
 
+    /**
+     * Blocks until an account of {@code type} is added.
+     */
+    // TODO(b/199077745): Remove poll once AccountManager race condition is fixed
+    private Bundle addAccountWithType(TestAppInstance testApp, String type) {
+        return Poll.forValue("created account bundle", () -> addAccountWithTypeOnce(testApp, type))
+                .toNotBeNull()
+                .errorOnFail()
+                .await();
+    }
+
     private Bundle addAccountWithTypeOnce(String type) throws Exception {
         return mAccountManager.addAccount(
                 type,
@@ -312,13 +305,23 @@
                 /* handler= */ null).getResult();
     }
 
+    private Bundle addAccountWithTypeOnce(TestAppInstance testApp, String type)
+            throws Exception {
+        return testApp.accountManager().addAccount(
+                type,
+                /* authTokenType= */ null,
+                /* requiredFeatures= */ null,
+                /* addAccountOptions= */ null,
+                /* activity= */ null,
+                /* callback= */ null,
+                /* handler= */ null).getResult();
+    }
+
     /**
      * Blocks until {@code account} is removed.
      */
-    // TODO(b/199077745): Remove sleep once AccountManager race condition is fixed
-    private Bundle removeAccount(Account account)
-            throws OperationCanceledException, IOException,
-            InterruptedException, AuthenticatorException {
+    private Bundle removeAccountFromInstrumentedApp(Account account)
+            throws Exception {
         return mAccountManager.removeAccount(
                 account,
                 /* activity= */ null,
@@ -326,4 +329,17 @@
                 /* handler= */ null)
                 .getResult();
     }
+
+    /**
+     * Blocks until {@code account} is removed.
+     */
+    private Bundle removeAccount(TestAppInstance testApp, Account account)
+            throws Exception {
+        return testApp.accountManager().removeAccount(
+                account,
+                /* activity= */ null,
+                /* callback= */  null,
+                /* handler= */ null)
+                .getResult();
+    }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
index 0c054e3..ec90f4f 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/ApplicationRestrictionsTest.java
@@ -15,6 +15,7 @@
  */
 package android.devicepolicy.cts;
 
+import static android.content.Context.RECEIVER_EXPORTED;
 import static android.content.Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED;
 
 import static com.android.bedstead.metricsrecorder.truth.MetricQueryBuilderSubject.assertThat;
@@ -303,7 +304,8 @@
         Bundle bundle = createBundle("setApplicationRestrictions_restrictionsChangedBroadcastIsReceived");
 
         try (TestAppInstance testApp = sTestApp.install()) {
-            testApp.registerReceiver(new IntentFilter(ACTION_APPLICATION_RESTRICTIONS_CHANGED));
+            testApp.registerReceiver(new IntentFilter(ACTION_APPLICATION_RESTRICTIONS_CHANGED),
+                    RECEIVER_EXPORTED);
 
             sDeviceState.dpc().devicePolicyManager()
                     .setApplicationRestrictions(
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
index 42453f3..8595c70 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileAppsTest.java
@@ -16,10 +16,10 @@
 
 package android.devicepolicy.cts;
 
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
-import static com.android.bedstead.harrier.DeviceState.UserType.WORK_PROFILE;
 import static com.android.bedstead.harrier.OptionalBoolean.FALSE;
 import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
index 3aaee38..791ca42 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/CrossProfileSharingTest.java
@@ -23,8 +23,8 @@
 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY;
 import static android.os.UserManager.DISALLOW_SHARE_INTO_MANAGED_PROFILE;
 
-import static com.android.bedstead.harrier.DeviceState.UserType.PRIMARY_USER;
-import static com.android.bedstead.harrier.DeviceState.UserType.WORK_PROFILE;
+import static com.android.bedstead.harrier.UserType.PRIMARY_USER;
+import static com.android.bedstead.harrier.UserType.WORK_PROFILE;
 import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
 import static com.android.queryable.queries.ActivityQuery.activity;
 import static com.android.queryable.queries.IntentFilterQuery.intentFilter;
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
index 77530c8..3a97388 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DelegationScopesTest.java
@@ -23,6 +23,7 @@
 import static android.app.admin.DevicePolicyManager.DELEGATION_NETWORK_LOGGING;
 import static android.app.admin.DevicePolicyManager.DELEGATION_SECURITY_LOGGING;
 import static android.app.admin.DevicePolicyManager.EXTRA_DELEGATION_SCOPES;
+import static android.content.Context.RECEIVER_EXPORTED;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -422,7 +423,8 @@
             testApp.activities().any().start();
             // TODO(b/198588980): automatically register every test app for this broadcast.
             testApp.registerReceiver(
-                    new IntentFilter(ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED));
+                    new IntentFilter(ACTION_APPLICATION_DELEGATION_SCOPES_CHANGED),
+                    RECEIVER_EXPORTED);
 
             try {
                 sDeviceState.dpc().devicePolicyManager().setDelegatedScopes(
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
index 264dd12..a9ba134 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DeviceOwnerPrerequisitesTest.java
@@ -30,8 +30,7 @@
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.Postsubmit;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDeviceOwner;
-import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoProfileOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.exceptions.AdbException;
 import com.android.bedstead.nene.utils.Poll;
@@ -85,8 +84,7 @@
 
     @Test
     @Postsubmit(reason = "new test with sleep")
-    @EnsureHasNoDeviceOwner
-    @EnsureHasNoProfileOwner
+    @EnsureHasNoDpc
     public void setDeviceOwnerViaAdb_deviceHasAccount_fails()
             throws InterruptedException {
         try (TestAppInstance accountAuthenticatorApp =
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
index 028bb31..80af6af 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/DevicePolicyManagerTest.java
@@ -18,8 +18,19 @@
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.app.AppOpsManager.MODE_ALLOWED;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_LOCALE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TIME_ZONE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_PASSWORD;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SECURITY_TYPE;
+import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_WIFI_SSID;
+import static android.app.admin.DevicePolicyManager.MIME_TYPE_PROVISIONING_NFC;
+import static android.content.pm.PackageManager.FEATURE_DEVICE_ADMIN;
+import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
+import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
+import static android.nfc.NfcAdapter.EXTRA_NDEF_MESSAGES;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -34,12 +45,16 @@
 import android.app.admin.ManagedProfileProvisioningParams;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.CrossProfileApps;
 import android.content.pm.PackageManager;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.os.Bundle;
+import android.os.Parcelable;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
 
 import androidx.test.core.app.ApplicationProvider;
 
@@ -47,13 +62,20 @@
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
 import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
+import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
 import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
 import com.android.bedstead.harrier.annotations.Postsubmit;
 import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
 import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireNotHeadlessSystemUserMode;
 import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasDeviceOwner;
 import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoDpc;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasNoProfileOwner;
+import com.android.bedstead.harrier.annotations.enterprise.EnsureHasProfileOwner;
 import com.android.bedstead.nene.TestApis;
 import com.android.bedstead.nene.permissions.PermissionContext;
 import com.android.compatibility.common.util.SystemUtil;
@@ -64,8 +86,13 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.util.stream.Collectors;
 
@@ -106,14 +133,34 @@
     private static final String REMOVE_ACTIVE_ADMIN_COMMAND =
             "dpm remove-active-admin --user cur " + DEVICE_ADMIN_COMPONENT_NAME.flattenToString();
 
+    private static final String NFC_INTENT_COMPONENT_NAME =
+            "com.test.dpc/com.test.dpc.DeviceAdminReceiver";
+    private static final String NFC_INTENT_PACKAGE_NAME =
+            "com.test.dpc.DeviceAdminReceiver";
+    private static final String NFC_INTENT_LOCALE = "en_US";
+    private static final String NFC_INTENT_TIMEZONE = "America/New_York";
+    private static final String NFC_INTENT_WIFI_SSID = "\"" + "TestWifiSsid" + "\"";
+    private static final String NFC_INTENT_WIFI_SECURITY_TYPE = "";
+    private static final String NFC_INTENT_WIFI_PASSWORD = "";
+    private static final String NFC_INTENT_BAD_ACTION = "badAction";
+    private static final String NFC_INTENT_BAD_MIME = "badMime";
+    private static final String NFC_INTENT_PROVISIONING_SAMPLE = "NFC provisioning sample";
+    private static final Intent NFC_INTENT_NO_NDEF_RECORD = new Intent(ACTION_NDEF_DISCOVERED);
+    private static final HashMap<String, String> NFC_DATA_VALID = createNfcIntentData();
+    private static final HashMap<String, String> NFC_DATA_EMPTY = new HashMap();
+    private static final Map<String, String> NFC_DATA_WITH_COMPONENT_NAME =
+            Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, NFC_INTENT_COMPONENT_NAME);
+    private static final Map<String, String> NFC_DATA_WITH_ADMIN_PACKAGE_NAME =
+            Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, NFC_INTENT_PACKAGE_NAME);
+
     @ClassRule
     @Rule
     public static final DeviceState sDeviceState = new DeviceState();
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedManagedProfile_createsProfile() throws Exception {
@@ -133,8 +180,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedManagedProfile_createsManagedProfile() throws Exception {
@@ -154,8 +201,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS, INTERACT_ACROSS_USERS_FULL})
     @Test
     public void newlyProvisionedManagedProfile_setsActiveAdmin() throws Exception {
@@ -177,8 +224,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS, INTERACT_ACROSS_USERS})
     @Test
     public void newlyProvisionedManagedProfile_setsProfileOwner() throws Exception {
@@ -199,8 +246,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Ignore
     @Test
@@ -224,8 +271,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedManagedProfile_removesAccountFromParentByDefault()
@@ -249,8 +296,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Ignore
     @Test
@@ -261,7 +308,7 @@
             ManagedProfileProvisioningParams params =
                     createManagedProfileProvisioningParamsBuilder()
                             .setAccountToMigrate(TEST_ACCOUNT)
-                            .setKeepAccountMigrated(true)
+                            .setKeepingAccountOnMigration(true)
                             .build();
             profile = provisionManagedProfile(params);
 
@@ -275,8 +322,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedManagedProfile_removesNonRequiredAppsFromProfile()
@@ -301,8 +348,8 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
-    @RequireFeature(PackageManager.FEATURE_MANAGED_USERS)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_MANAGED_USERS)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedManagedProfile_setsCrossProfilePackages()
@@ -412,109 +459,123 @@
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
     @EnsureHasPermission({MANAGE_PROFILE_AND_DEVICE_OWNERS})
     @Test
     public void newlyProvisionedFullyManagedDevice_setsDeviceOwner() throws Exception {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
         try {
+
             FullyManagedDeviceProvisioningParams params =
                     createDefaultManagedDeviceProvisioningParamsBuilder().build();
-            resetUserSetupCompletedFlag();
             sDevicePolicyManager.provisionFullyManagedDevice(params);
 
             assertThat(sDevicePolicyManager.isDeviceOwnerApp(sContext.getPackageName())).isTrue();
+
         } finally {
             sDevicePolicyManager.forceRemoveActiveAdmin(
                     DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
-            setUserSetupCompletedFlag();
+            TestApis.users().current().setSetupComplete(setupComplete);
         }
     }
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedFullyManagedDevice_doesNotThrowException() throws Exception {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
         try {
+
             FullyManagedDeviceProvisioningParams params =
                     createDefaultManagedDeviceProvisioningParamsBuilder().build();
-            resetUserSetupCompletedFlag();
             sDevicePolicyManager.provisionFullyManagedDevice(params);
+
         } finally {
             sDevicePolicyManager.forceRemoveActiveAdmin(
                     DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
-            setUserSetupCompletedFlag();
+            TestApis.users().current().setSetupComplete(setupComplete);
         }
     }
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedFullyManagedDevice_canControlSensorPermissionGrantsByDefault()
             throws Exception {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
         try {
+
             FullyManagedDeviceProvisioningParams params =
                     createDefaultManagedDeviceProvisioningParamsBuilder().build();
-            resetUserSetupCompletedFlag();
             sDevicePolicyManager.provisionFullyManagedDevice(params);
 
             assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isTrue();
+
         } finally {
             sDevicePolicyManager.forceRemoveActiveAdmin(
                     DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
-            setUserSetupCompletedFlag();
+            TestApis.users().current().setSetupComplete(setupComplete);
         }
     }
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Postsubmit(reason = "b/181993922 automatically marked flaky")
     @Test
     public void newlyProvisionedFullyManagedDevice_canOptOutOfControllingSensorPermissionGrants()
             throws Exception {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
         try {
+
             FullyManagedDeviceProvisioningParams params =
                     createDefaultManagedDeviceProvisioningParamsBuilder()
-                            .setDeviceOwnerCanGrantSensorsPermissions(false)
+                            .setCanDeviceOwnerGrantSensorsPermissions(false)
                             .build();
-            resetUserSetupCompletedFlag();
             sDevicePolicyManager.provisionFullyManagedDevice(params);
 
             assertThat(sDevicePolicyManager.canAdminGrantSensorsPermissions()).isFalse();
+
         } finally {
             sDevicePolicyManager.forceRemoveActiveAdmin(
                     DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
-            setUserSetupCompletedFlag();
+            TestApis.users().current().setSetupComplete(setupComplete);
         }
     }
 
     @RequireRunOnPrimaryUser
     @EnsureHasNoDpc
-    @RequireFeature(PackageManager.FEATURE_DEVICE_ADMIN)
+    @RequireFeature(FEATURE_DEVICE_ADMIN)
     @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
     @Test
     public void newlyProvisionedFullyManagedDevice_leavesAllSystemAppsEnabledWhenRequested()
             throws Exception {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
         try {
+            Set<String> systemAppsBeforeProvisioning = findSystemApps();
+
             FullyManagedDeviceProvisioningParams params =
                     createDefaultManagedDeviceProvisioningParamsBuilder()
                             .setLeaveAllSystemAppsEnabled(true)
                             .build();
-            resetUserSetupCompletedFlag();
             sDevicePolicyManager.provisionFullyManagedDevice(params);
-            Set<String> systemAppsBeforeProvisioning = findSystemApps();
 
             Set<String> systemAppsAfterProvisioning = findSystemApps();
             assertThat(systemAppsAfterProvisioning).isEqualTo(systemAppsBeforeProvisioning);
         } finally {
             sDevicePolicyManager.forceRemoveActiveAdmin(
                     DEVICE_ADMIN_COMPONENT_NAME, sContext.getUserId());
-            setUserSetupCompletedFlag();
+            TestApis.users().current().setSetupComplete(setupComplete);
         }
     }
 
@@ -538,20 +599,6 @@
                 .setLeaveAllSystemAppsEnabled(true);
     }
 
-    private void resetUserSetupCompletedFlag() {
-        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
-            Settings.Secure.putInt(sContext.getContentResolver(), USER_SETUP_COMPLETE_KEY, 0);
-        }
-        sDevicePolicyManager.forceUpdateUserSetupComplete(sContext.getUserId());
-    }
-
-    private void setUserSetupCompletedFlag() {
-        try (PermissionContext p = TestApis.permissions().withPermission(WRITE_SECURE_SETTINGS)) {
-            Settings.Secure.putInt(sContext.getContentResolver(), USER_SETUP_COMPLETE_KEY, 1);
-        }
-        sDevicePolicyManager.forceUpdateUserSetupComplete(sContext.getUserId());
-    }
-
     private Set<String> findSystemApps() {
         return sPackageManager.getInstalledApplications(PackageManager.MATCH_SYSTEM_ONLY)
                 .stream()
@@ -637,6 +684,86 @@
                 .collect(Collectors.toSet());
     }
 
+    @Test
+    public void createProvisioningIntentFromNfcIntent_validNfcIntent_returnsValidIntent()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_VALID);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNotNull();
+        assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+                .containsAtLeastEntriesIn(NFC_DATA_VALID);
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_noComponentNorPackage_returnsNull()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_EMPTY);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNull();
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_withComponent_returnsValidIntent()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_WITH_COMPONENT_NAME);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNotNull();
+        assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+                .containsAtLeastEntriesIn(NFC_DATA_WITH_COMPONENT_NAME);
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_withPackage_returnsValidIntent()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentFromMap(NFC_DATA_WITH_ADMIN_PACKAGE_NAME);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNotNull();
+        assertThat(provisioningBundleToMap(provisioningIntent.getExtras()))
+                .containsAtLeastEntriesIn(NFC_DATA_WITH_ADMIN_PACKAGE_NAME);
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_badIntentAction_returnsNull()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentWithAction(NFC_INTENT_BAD_ACTION);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNull();
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_badMimeType_returnsNull()
+            throws IOException {
+        Intent nfcIntent = createNfcIntentWithMimeType(NFC_INTENT_BAD_MIME);
+
+        Intent provisioningIntent =
+                sDevicePolicyManager.createProvisioningIntentFromNfcIntent(nfcIntent);
+
+        assertThat(provisioningIntent).isNull();
+    }
+
+    @Test
+    public void createProvisioningIntentFromNfcIntent_doesNotIncludeNdefRecord_returnsNull() {
+        Intent provisioningIntent = sDevicePolicyManager
+                .createProvisioningIntentFromNfcIntent(NFC_INTENT_NO_NDEF_RECORD);
+
+        assertThat(provisioningIntent).isNull();
+    }
+
     @EnsureHasDeviceOwner
     @Test
     public void getCameraDisabled_adminPassedDoesNotBelongToCaller_throwsException() {
@@ -667,4 +794,419 @@
         sDevicePolicyManager.removeActiveAdmin(
                 sDeviceState.deviceOwner().componentName());
     }
+
+    private static HashMap<String, String> createNfcIntentData() {
+        HashMap<String, String> nfcIntentInput = new HashMap<String, String>();
+        nfcIntentInput.putAll(
+                Map.of(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME, NFC_INTENT_COMPONENT_NAME,
+                EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_NAME, NFC_INTENT_PACKAGE_NAME,
+                EXTRA_PROVISIONING_LOCALE, NFC_INTENT_LOCALE,
+                EXTRA_PROVISIONING_TIME_ZONE, NFC_INTENT_TIMEZONE,
+                EXTRA_PROVISIONING_WIFI_SSID, NFC_INTENT_WIFI_SSID,
+                EXTRA_PROVISIONING_WIFI_SECURITY_TYPE, NFC_INTENT_WIFI_SECURITY_TYPE,
+                EXTRA_PROVISIONING_WIFI_PASSWORD, NFC_INTENT_WIFI_PASSWORD)
+        );
+
+        return nfcIntentInput;
+    }
+
+    private Intent createNfcIntentWithAction(String action)
+            throws IOException {
+        return createNfcIntent(NFC_DATA_VALID, action, MIME_TYPE_PROVISIONING_NFC);
+    }
+
+    private Intent createNfcIntentWithMimeType(String mime)
+            throws IOException {
+        return createNfcIntent(NFC_DATA_VALID, ACTION_NDEF_DISCOVERED, mime);
+    }
+
+    private Intent createNfcIntentFromMap(Map<String, String> input)
+            throws IOException {
+        return createNfcIntent(input, ACTION_NDEF_DISCOVERED, MIME_TYPE_PROVISIONING_NFC);
+    }
+
+    private Intent createNfcIntent(Map<String, String> input, String action, String mime)
+            throws IOException {
+        Intent nfcIntent = new Intent(action);
+        Parcelable[] nfcMessages =
+                new Parcelable[]{createNdefMessage(input, mime)};
+        nfcIntent.putExtra(EXTRA_NDEF_MESSAGES, nfcMessages);
+
+        return nfcIntent;
+    }
+
+    private Map<String, String> provisioningBundleToMap(Bundle bundle) {
+        Map<String, String> map = new HashMap();
+
+        for (String key : bundle.keySet()) {
+            if(EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME.equals(key)) {
+                ComponentName componentName = bundle.getParcelable(key);
+                map.put(key, componentName.getPackageName() + "/" + componentName.getClassName());
+            }
+            else {
+                map.put(key, bundle.getString(key));
+            }
+        }
+
+        return map;
+    }
+
+    private NdefMessage createNdefMessage(Map<String, String> provisioningValues, String mime)
+            throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        Properties properties = new Properties();
+        // Store all the values into the Properties object
+        for (Map.Entry<String, String> e : provisioningValues.entrySet()) {
+            properties.put(e.getKey(), e.getValue());
+        }
+
+        properties.store(stream, NFC_INTENT_PROVISIONING_SAMPLE);
+        NdefRecord record = NdefRecord.createMime(mime, stream.toByteArray());
+
+        return new NdefMessage(new NdefRecord[]{record});
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureDoesNotHavePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void checkProvisioningPreCondition_withoutRequiredPermission_throwsSecurityException() {
+        assertThrows(SecurityException.class, () ->
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()));
+
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void checkProvisioningPreCondition_withRequiredPermission_doesNotThrowSecurityException() {
+        sDevicePolicyManager.checkProvisioningPreCondition(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                DEVICE_ADMIN_COMPONENT_NAME.getPackageName());
+
+        // Doesn't throw exception.
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireDoesNotHaveFeature(FEATURE_DEVICE_ADMIN)
+    public void checkProvisioningPreCondition_withoutDeviceAdminFeature_returnsDeviceAdminNotSupported() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    public void checkProvisioningPreCondition_actionPO_returnsOk() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_OK);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireDoesNotHaveFeature(FEATURE_MANAGED_USERS)
+    public void checkProvisioningPreCondition_actionPO_withoutManagedUserFeature_returnsManagedUsersNotSupported() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasProfileOwner
+    @RequireRunOnSecondaryUser
+    public void checkProvisioningPreCondition_actionPO_onManagedUser_returnsHasProfileOwner() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnWorkProfile
+    public void checkProvisioningPreCondition_actionPO_onManagedProfile_returnsHasProfileOwner() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasDeviceOwner
+    public void checkProvisioningPreCondition_actionPO_onManagedDevice_returnsCanNotAddManagedProfile() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasWorkProfile
+    @RequireRunOnPrimaryUser
+    public void checkProvisioningPreCondition_actionPO_withWorkProfile_returnsCanNotAddManagedProfile() {
+        assertThat(
+                sDevicePolicyManager.checkProvisioningPreCondition(
+                        DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
+                        DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                .isEqualTo(DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_returnsOk() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_OK);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasNoDpc
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_setupComplete_returnsUserSetupCompleted() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(true);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnPrimaryUser
+    @EnsureHasDeviceOwner
+    @EnsureHasNoWorkProfile
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_onManagedDevice_returnsHasDeviceOwner() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnWorkProfile
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_onManagedProfile_returnsHasProfileOwner() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnSecondaryUser
+    @EnsureHasProfileOwner
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_onManagedUser_returnsHasProfileOwner() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_USER_HAS_PROFILE_OWNER);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnSecondaryUser
+    @EnsureHasNoProfileOwner
+    @RequireNotHeadlessSystemUserMode
+    public void checkProvisioningPreCondition_actionDO_onNonSystemUser_returnsNotSystemUser() {
+        boolean setupComplete = TestApis.users().current().getSetupComplete();
+        TestApis.users().current().setSetupComplete(false);
+
+        try {
+            assertThat(
+                    sDevicePolicyManager.checkProvisioningPreCondition(
+                            DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
+                            DEVICE_ADMIN_COMPONENT_NAME.getPackageName()))
+                    .isEqualTo(DevicePolicyManager.CODE_NOT_SYSTEM_USER);
+
+        } finally {
+            TestApis.users().current().setSetupComplete(setupComplete);
+        }
+    }
+
+    // TODO(b/208843126): add more CTS coverage for setUserProvisioningState
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureDoesNotHavePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    public void setUserProvisioningState_withoutRequiredPermission_throwsSecurityException() {
+        assertThrows(SecurityException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_UNMANAGED,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @RequireRunOnWorkProfile
+    public void setUserProvisioningState_withRequiredPermission_doesNotThrowSecurityException() {
+        sDevicePolicyManager.setUserProvisioningState(
+                DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+                TestApis.users().current().userHandle());
+
+        // Doesn't throw exception.
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_unmanagedDevice_stateUserSetupIncomplete_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_SETUP_INCOMPLETE,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_unmanagedDevice_stateUserSetupComplete_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_SETUP_COMPLETE,
+                        TestApis.users().current().userHandle()));
+
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_unmanagedDevice_stateUserSetupFinalized_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_SETUP_FINALIZED,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_unmanagedDevice_stateUserProfileComplete_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_PROFILE_COMPLETE,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_unmanagedDevice_stateUserProfileFinalized_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasNoDpc
+    public void setUserProvisioningState_settingToSameState_throwIllegalStateException() {
+        assertThrows(IllegalStateException.class, () ->
+                sDevicePolicyManager.setUserProvisioningState(
+                        DevicePolicyManager.STATE_USER_UNMANAGED,
+                        TestApis.users().current().userHandle()));
+    }
+
+    @Postsubmit(reason = "New test")
+    @Test
+    @EnsureHasPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)
+    @EnsureHasDeviceOwner
+    public void setUserProvisioningState_unmanagedDevice_stateUserUnmanaged_doesNotThrowIllegalStateException() {
+        sDevicePolicyManager.setUserProvisioningState(
+                DevicePolicyManager.STATE_USER_PROFILE_FINALIZED,
+                TestApis.users().current().userHandle());
+
+        assertThat(sDevicePolicyManager.getUserProvisioningState())
+                .isEqualTo(DevicePolicyManager.STATE_USER_PROFILE_FINALIZED);
+    }
 }
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java b/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
new file mode 100644
index 0000000..fac8337
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/EnterpriseResourcesTests.java
@@ -0,0 +1,366 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.devicepolicy.cts;
+
+import static android.Manifest.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES;
+import static android.app.admin.DevicePolicyResources.Drawable.INVALID_ID;
+import static android.app.admin.DevicePolicyResources.Drawable.Style.SOLID_COLORED;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_ICON;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_ICON_BADGE;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_OFF_ICON;
+import static android.app.admin.DevicePolicyResources.Drawable.WORK_PROFILE_USER_ICON;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.app.admin.DevicePolicyDrawableResource;
+import android.app.admin.DevicePolicyManager;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+// TODO(b/208084779): Add more cts tests to cover setting different styles and sources, also
+//  add more tests to cover calling from other packages after adding support for the new APIs in
+//  the test sdk.
+@RunWith(BedsteadJUnit4.class)
+public class EnterpriseResourcesTests {
+    private static final String TAG = "EnterpriseResourcesTests";
+
+    private static final Context sContext = TestApis.context().instrumentedContext();
+    private static final DevicePolicyManager sDpm =
+            sContext.getSystemService(DevicePolicyManager.class);
+
+    private static final int UPDATABLE_DRAWABLE_1 = WORK_PROFILE_ICON_BADGE;
+    private static final int UPDATABLE_DRAWABLE_2 = WORK_PROFILE_ICON;
+    private static final int DRAWABLE_STYLE_1 = SOLID_COLORED;
+    private static final int INVALID_DRAWABLE_RESOURCE_ID = -1;
+
+    @ClassRule
+    @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    @After
+    public void tearDown() {
+        resetAllDrawables();
+    }
+
+    @Before
+    public void setup() {
+        resetAllDrawables();
+    }
+
+    private void resetAllDrawables() {
+        try (PermissionContext p = TestApis.permissions().withPermission(
+                UPDATE_DEVICE_MANAGEMENT_RESOURCES)) {
+            sDpm.resetDrawables(
+                    new int[]{
+                            WORK_PROFILE_ICON_BADGE,
+                            WORK_PROFILE_ICON,
+                            WORK_PROFILE_OFF_ICON,
+                            WORK_PROFILE_USER_ICON});
+        }
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureDoesNotHavePermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_withoutRequiredPermission_throwsSecurityException() {
+        assertThrows(SecurityException.class, () ->
+                sDpm.setDrawables(createDrawable(
+                        UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1)));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_withRequiredPermission_doesNotThrowSecurityException() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_withInvalidUpdatableDrawableId_throwsIllegalArgumentException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                sDpm.setDrawables(createDrawable(
+                        INVALID_ID, DRAWABLE_STYLE_1, R.drawable.test_drawable_1)));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_updatesCorrectUpdatableDrawable() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        Drawable drawable = sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_1,
+                DRAWABLE_STYLE_1,
+                /* default= */ () -> null);
+        assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_1)))
+                .isTrue();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_updatesCurrentlyUpdatedDrawable() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+        Drawable drawable = sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null);
+        assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_2)))
+                .isTrue();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_doesNotUpdateOtherUpdatableDrawables() {
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_2});
+
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        assertThat(
+                sDpm.getDrawable(UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, /* default= */ () -> null))
+                .isNull();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_drawableChangedFromNull_sendsBroadcast() {
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        broadcastReceiver.awaitForBroadcastOrFail();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void setDrawables_drawableChangedFromOtherDrawable_sendsBroadcast() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+        broadcastReceiver.awaitForBroadcastOrFail();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    @Ignore("b/208237942")
+    public void setDrawables_drawableNotChanged_doesNotSendBroadcast() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        assertThat(broadcastReceiver.awaitForBroadcast()).isNull();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureDoesNotHavePermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetDrawables_withoutRequiredPermission_throwsSecurityException() {
+        assertThrows(SecurityException.class, () -> sDpm.resetDrawables(
+                new int[]{UPDATABLE_DRAWABLE_1}));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetDrawables_withRequiredPermission_doesNotThrowSecurityException() {
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetDrawables_removesPreviouslysetDrawables() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+        assertThat(
+                sDpm.getDrawable(UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null))
+                .isNull();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetDrawables_doesNotResetOtherSetDrawables() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, R.drawable.test_drawable_2));
+
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+        Drawable drawable = sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_2, DRAWABLE_STYLE_1, /* default= */ () -> null);
+        assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_2)))
+                .isTrue();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void resetDrawables_drawableChanged_sendsBroadcast() {
+        sDpm.setDrawables(createDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+        broadcastReceiver.awaitForBroadcastOrFail();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    @Ignore("b/208237942")
+    public void resetDrawables_drawableNotChanged_doesNotSendBroadcast() {
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+        BlockingBroadcastReceiver broadcastReceiver = sDeviceState.registerBroadcastReceiver(
+                DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED);
+
+        sDpm.resetDrawables(new int[]{UPDATABLE_DRAWABLE_1});
+
+        assertThat(broadcastReceiver.awaitForBroadcast()).isNull();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    public void getDrawable_drawableIsSet_returnsUpdatedDrawable() {
+        try (PermissionContext p = TestApis.permissions().withPermission(
+                UPDATE_DEVICE_MANAGEMENT_RESOURCES)) {
+            sDpm.setDrawables(createDrawable(
+                    UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.drawable.test_drawable_1));
+        }
+
+        Drawable drawable = sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> null);
+
+        assertThat(areSameDrawables(drawable, sContext.getDrawable(R.drawable.test_drawable_1)))
+                .isTrue();
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    public void getDrawable_drawableIsNotSet_returnsDefaultDrawable() {
+        Drawable defaultDrawable = sContext.getDrawable(R.drawable.test_drawable_1);
+
+        Drawable drawable = sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, /* default= */ () -> defaultDrawable);
+
+        assertThat(drawable).isEqualTo(defaultDrawable);
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    public void getDrawable_defaultIsNull_throwsException() {
+        assertThrows(NullPointerException.class, () -> sDpm.getDrawable(
+                UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, null));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void constructDevicePolicyDrawableResource_withNonExistentDrawable_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () -> new DevicePolicyDrawableResource(
+                sContext, UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, INVALID_DRAWABLE_RESOURCE_ID));
+    }
+
+    @Test
+    @Postsubmit(reason = "New test")
+    @EnsureHasPermission(UPDATE_DEVICE_MANAGEMENT_RESOURCES)
+    public void constructDevicePolicyDrawableResourc_withNonDrawableResource_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class, () -> new DevicePolicyDrawableResource(
+                sContext, UPDATABLE_DRAWABLE_1, DRAWABLE_STYLE_1, R.string.test_string));
+    }
+
+    // TODO(b/16348282): extract to a common place to make it reusable.
+    private static boolean areSameDrawables(Drawable drawable1, Drawable drawable2) {
+        return drawable1 == drawable2 || getBitmap(drawable1).sameAs(getBitmap(drawable2));
+    }
+
+    private static Bitmap getBitmap(Drawable drawable) {
+        int width = drawable.getIntrinsicWidth();
+        int height = drawable.getIntrinsicHeight();
+        // Some drawables have no intrinsic width - e.g. solid colours.
+        if (width <= 0) {
+            width = 1;
+        }
+        if (height <= 0) {
+            height = 1;
+        }
+        Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(result);
+        drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        drawable.draw(canvas);
+        return result;
+    }
+
+    private Set<DevicePolicyDrawableResource> createDrawable(
+            int updatableDrawableId, int style, int resourceId) {
+        return Set.of(new DevicePolicyDrawableResource(
+                sContext, updatableDrawableId, style, resourceId));
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
index 6f9128d..ae071c2 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LockTaskTest.java
@@ -54,6 +54,7 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.IntTestParameter;
 import com.android.bedstead.harrier.annotations.Postsubmit;
 import com.android.bedstead.harrier.annotations.RequireFeature;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
@@ -76,6 +77,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Set;
 
 @RunWith(BedsteadJUnit4.class)
@@ -88,20 +91,24 @@
     private static final DevicePolicyManager sLocalDevicePolicyManager =
             TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
 
-    private static final int[] INDIVIDUALLY_SETTABLE_FLAGS = new int[]{
+    @IntTestParameter({
             LOCK_TASK_FEATURE_SYSTEM_INFO,
             LOCK_TASK_FEATURE_HOME,
             LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
-            LOCK_TASK_FEATURE_KEYGUARD
-    };
+            LOCK_TASK_FEATURE_KEYGUARD})
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface IndividuallySettableFlagTestParameter {
+    }
 
-    private static final int[] FLAGS_SETTABLE_WITH_HOME = new int[]{
+    @IntTestParameter({
             LOCK_TASK_FEATURE_SYSTEM_INFO,
             LOCK_TASK_FEATURE_OVERVIEW,
             LOCK_TASK_FEATURE_NOTIFICATIONS,
             LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
-            LOCK_TASK_FEATURE_KEYGUARD
-    };
+            LOCK_TASK_FEATURE_KEYGUARD})
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface SettableWithHomeFlagTestParameter {
+    }
 
     private static final TestAppProvider sTestAppProvider = new TestAppProvider();
     private static final TestApp sLockTaskTestApp = sTestAppProvider.query()
@@ -322,20 +329,19 @@
     @PositivePolicyTest(policy = LockTask.class)
     // TODO(b/188893663): Support additional parameterization for cases like this
     @Postsubmit(reason = "b/181993922 automatically marked flaky")
-    public void setLockTaskFeatures_overviewFeature_setsFeature() {
+    public void setLockTaskFeatures_individuallySettableFlag_setsFeature(
+            @IndividuallySettableFlagTestParameter int flag) {
         int originalLockTaskFeatures =
                 sDeviceState.dpc().devicePolicyManager()
                         .getLockTaskFeatures(DPC_COMPONENT_NAME);
 
         try {
-            for (int flag : INDIVIDUALLY_SETTABLE_FLAGS) {
-                sDeviceState.dpc().devicePolicyManager()
-                        .setLockTaskFeatures(DPC_COMPONENT_NAME, flag);
+            sDeviceState.dpc().devicePolicyManager()
+                    .setLockTaskFeatures(DPC_COMPONENT_NAME, flag);
 
-                assertThat(sDeviceState.dpc().devicePolicyManager()
-                        .getLockTaskFeatures(DPC_COMPONENT_NAME))
-                        .isEqualTo(flag);
-            }
+            assertThat(sDeviceState.dpc().devicePolicyManager()
+                    .getLockTaskFeatures(DPC_COMPONENT_NAME))
+                    .isEqualTo(flag);
         } finally {
             sDeviceState.dpc().devicePolicyManager()
                     .setLockTaskFeatures(DPC_COMPONENT_NAME, originalLockTaskFeatures);
@@ -387,20 +393,19 @@
     @PositivePolicyTest(policy = LockTask.class)
     // TODO(b/188893663): Support additional parameterization for cases like this
     @Postsubmit(reason = "b/181993922 automatically marked flaky")
-    public void setLockTaskFeatures_multipleFeatures_setsFeatures() {
+    public void setLockTaskFeatures_multipleFeatures_setsFeatures(
+            @SettableWithHomeFlagTestParameter int flag) {
         int originalLockTaskFeatures =
                 sDeviceState.dpc().devicePolicyManager()
                         .getLockTaskFeatures(DPC_COMPONENT_NAME);
 
         try {
-            for (int flag : FLAGS_SETTABLE_WITH_HOME) {
-                sDeviceState.dpc().devicePolicyManager()
-                        .setLockTaskFeatures(DPC_COMPONENT_NAME, LOCK_TASK_FEATURE_HOME | flag);
+            sDeviceState.dpc().devicePolicyManager()
+                    .setLockTaskFeatures(DPC_COMPONENT_NAME, LOCK_TASK_FEATURE_HOME | flag);
 
-                assertThat(sDeviceState.dpc().devicePolicyManager()
-                        .getLockTaskFeatures(DPC_COMPONENT_NAME))
-                        .isEqualTo(LOCK_TASK_FEATURE_HOME | flag);
-            }
+            assertThat(sDeviceState.dpc().devicePolicyManager()
+                    .getLockTaskFeatures(DPC_COMPONENT_NAME))
+                    .isEqualTo(LOCK_TASK_FEATURE_HOME | flag);
         } finally {
             sDeviceState.dpc().devicePolicyManager()
                     .setLockTaskFeatures(DPC_COMPONENT_NAME, originalLockTaskFeatures);
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java
new file mode 100644
index 0000000..74d8ebc
--- /dev/null
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/LockTest.java
@@ -0,0 +1,195 @@
+/*
+ * 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.devicepolicy.cts;
+
+import static android.Manifest.permission.LOCK_DEVICE;
+import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
+import static android.os.Build.VERSION_CODES.N;
+import static android.os.Build.VERSION_CODES.O;
+
+import static com.android.bedstead.harrier.Defaults.DEFAULT_PASSWORD;
+import static com.android.bedstead.metricsrecorder.truth.MetricQueryBuilderSubject.assertThat;
+import static com.android.bedstead.remotedpc.RemoteDpc.DPC_COMPONENT_NAME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.KeyguardManager;
+import android.app.admin.DevicePolicyManager;
+import android.stats.devicepolicy.EventId;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsurePasswordNotSet;
+import com.android.bedstead.harrier.annotations.EnsurePasswordSet;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
+import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireDoesNotHaveFeature;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.harrier.annotations.RequireTargetSdkVersion;
+import com.android.bedstead.harrier.annotations.enterprise.PositivePolicyTest;
+import com.android.bedstead.harrier.policies.DeprecatedResetPassword;
+import com.android.bedstead.harrier.policies.LockNow;
+import com.android.bedstead.metricsrecorder.EnterpriseMetricsRecorder;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.utils.Poll;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(BedsteadJUnit4.class)
+@RequireFeature("android.software.secure_lock_screen")
+public class LockTest {
+
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    private static final DevicePolicyManager sLocalDevicePolicyManager =
+            TestApis.context().instrumentedContext().getSystemService(DevicePolicyManager.class);
+    private static final KeyguardManager sLocalKeyguardManager =
+            TestApis.context().instrumentedContext().getSystemService(KeyguardManager.class);
+
+    // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+    @RequireFeature("android.software.secure_lock_screen")
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @EnsureHasPermission(LOCK_DEVICE)
+    @EnsureScreenIsOn
+    @EnsurePasswordNotSet
+    @Postsubmit(reason = "New test")
+    @Test
+    public void lockNow_permission_noPasswordSet_turnsScreenOff() throws Exception {
+        sLocalDevicePolicyManager.lockNow();
+
+        Poll.forValue("isScreenOn", () -> TestApis.device().isScreenOn())
+                .toBeEqualTo(false)
+                .errorOnFail()
+                .await();
+    }
+
+    // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+    @RequireFeature("android.software.secure_lock_screen")
+    @RequireFeature(FEATURE_AUTOMOTIVE)
+    @EnsureHasPermission(LOCK_DEVICE)
+    @EnsureScreenIsOn
+    @EnsurePasswordNotSet
+    @Postsubmit(reason = "New test")
+    @Test
+    public void lockNow_permission_automotive_noPasswordSet_doesNotTurnScreenOff()
+            throws Exception {
+        sLocalDevicePolicyManager.lockNow();
+
+        assertThat(TestApis.device().isScreenOn()).isTrue();
+    }
+
+    // TODO(191637162): When @PositivePolicyTest supports permissions, remove
+    @RequireFeature("android.software.secure_lock_screen")
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @EnsureHasPermission(LOCK_DEVICE)
+    @EnsureScreenIsOn
+    @EnsurePasswordSet
+    @Postsubmit(reason = "New test")
+    @Test
+    public void lockNow_permission_passwordSet_locksDevice() throws Exception {
+        sLocalDevicePolicyManager.lockNow();
+
+        Poll.forValue("isDeviceLocked", sLocalKeyguardManager::isDeviceLocked)
+                .toBeEqualTo(true)
+                .errorOnFail()
+                .await();
+    }
+
+    @Test
+    @PositivePolicyTest(policy = LockNow.class)
+    @Postsubmit(reason = "New test")
+    @EnsurePasswordNotSet
+    public void lockNow_logsMetric() {
+        try (EnterpriseMetricsRecorder metrics = EnterpriseMetricsRecorder.create()) {
+            sDeviceState.dpc().devicePolicyManager().lockNow(/* flags= */ 0);
+
+            assertThat(metrics.query()
+                    .whereType().isEqualTo(EventId.LOCK_NOW_VALUE)
+                    .whereAdminPackageName().isEqualTo(DPC_COMPONENT_NAME.getPackageName())
+                    .whereInteger().isEqualTo(0)
+            ).wasLogged();
+        }
+    }
+
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @EnsureScreenIsOn
+    @EnsurePasswordNotSet
+    @Postsubmit(reason = "New test")
+    @Test
+    @PositivePolicyTest(policy = LockNow.class)
+    public void lockNow_noPasswordSet_turnsScreenOff() throws Exception {
+        sDeviceState.dpc().devicePolicyManager().lockNow();
+
+        Poll.forValue("isScreenOn", () -> TestApis.device().isScreenOn())
+                .toBeEqualTo(false)
+                .errorOnFail()
+                .await();
+    }
+
+    @RequireFeature(FEATURE_AUTOMOTIVE)
+    @EnsureScreenIsOn
+    @EnsurePasswordNotSet
+    @Postsubmit(reason = "New test")
+    @Test
+    @PositivePolicyTest(policy = LockNow.class)
+    public void lockNow_automotive_noPasswordSet_doesNotTurnScreenOff() throws Exception {
+        sDeviceState.dpc().devicePolicyManager().lockNow();
+
+        assertThat(TestApis.device().isScreenOn()).isTrue();
+    }
+
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @EnsureScreenIsOn
+    @EnsurePasswordSet
+    @Postsubmit(reason = "New test")
+    @Test
+    @PositivePolicyTest(policy = LockNow.class)
+    public void lockNow_passwordSet_locksDevice() throws Exception {
+        sDeviceState.dpc().devicePolicyManager().lockNow();
+
+        Poll.forValue("isDeviceLocked", sLocalKeyguardManager::isDeviceLocked)
+                .toBeEqualTo(true)
+                .errorOnFail()
+                .await();
+    }
+
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @RequireTargetSdkVersion(max = N)
+    @Test
+    @PositivePolicyTest(policy = DeprecatedResetPassword.class)
+    public void resetPassword_targetBeforeN_returnsFalse() {
+        assertThat(sDeviceState.dpc()
+                .devicePolicyManager().resetPassword(DEFAULT_PASSWORD, /* flags= */ 0)).isFalse();
+    }
+
+    @RequireDoesNotHaveFeature(FEATURE_AUTOMOTIVE)
+    @RequireTargetSdkVersion(min = O)
+    @Test
+    @PositivePolicyTest(policy = DeprecatedResetPassword.class)
+    public void resetPassword_targetAfterO_throwsSecurityException() {
+        assertThrows(SecurityException.class,
+                () -> sDeviceState.dpc().devicePolicyManager()
+                        .resetPassword(DEFAULT_PASSWORD, /* flags= */ 0));
+    }
+}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
index 4545f94..bcebff2 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/PermissionGrantTest.java
@@ -41,6 +41,7 @@
 import com.android.bedstead.harrier.annotations.AfterClass;
 import com.android.bedstead.harrier.annotations.BeforeClass;
 import com.android.bedstead.harrier.annotations.NotificationsTest;
+import com.android.bedstead.harrier.annotations.StringTestParameter;
 import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
 import com.android.bedstead.harrier.annotations.enterprise.NegativePolicyTest;
@@ -54,14 +55,15 @@
 import com.android.bedstead.testapp.TestAppInstance;
 import com.android.bedstead.testapp.TestAppProvider;
 
-import com.google.common.collect.ImmutableSet;
-
 import org.junit.ClassRule;
 import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 @RunWith(BedsteadJUnit4.class)
 public final class PermissionGrantTest {
 
@@ -80,24 +82,40 @@
 
     private static final String DEVELOPMENT_PERMISSION = INTERACT_ACROSS_USERS;
 
-    private static final ImmutableSet<String> SENSOR_PERMISSIONS = ImmutableSet.of(
+    @StringTestParameter({
             ACCESS_FINE_LOCATION,
             ACCESS_BACKGROUND_LOCATION,
             ACCESS_COARSE_LOCATION,
             CAMERA,
             ACTIVITY_RECOGNITION,
-            BODY_SENSORS);
+            BODY_SENSORS})
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface SensorPermissionTestParameter {
+    }
 
-    private static final ImmutableSet<String> LOCATION_PERMISSIONS = ImmutableSet.of(
+    @StringTestParameter({
             ACCESS_FINE_LOCATION,
             ACCESS_BACKGROUND_LOCATION,
-            ACCESS_COARSE_LOCATION);
+            ACCESS_COARSE_LOCATION})
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface LocationPermissionTestParameter {
+    }
 
-    private static final ImmutableSet<String> DENYABLE_PERMISSIONS = ImmutableSet.<String>builder()
-            .add(GRANTABLE_PERMISSION)
-            .add(READ_SMS) // All DPCs can deny sms permission
-            .addAll(SENSOR_PERMISSIONS) // All DPCs can deny sensor permissions
-            .build();
+    @StringTestParameter({
+            // Grantable permission
+            READ_CALENDAR,
+            READ_SMS, // All DPCs can deny sms permission
+            // All DPCs can deny sensor permissions
+            ACCESS_FINE_LOCATION,
+            ACCESS_BACKGROUND_LOCATION,
+            ACCESS_COARSE_LOCATION,
+            CAMERA,
+            ACTIVITY_RECOGNITION,
+            BODY_SENSORS
+    })
+    @Retention(RetentionPolicy.RUNTIME)
+    private @interface DeniablePermissionTestParameter {
+    }
 
     private static final String NON_EXISTING_PACKAGE_NAME = "non.existing.package";
     private static final String NOT_DECLARED_PERMISSION = "not.declared.permission";
@@ -158,62 +176,57 @@
 
     @Test
     @NegativePolicyTest(policy = SetSensorPermissionGranted.class)
-    public void getPermissionGrantState_sensorPermission_notAbleToSetState_alsoCantReadState() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : SENSOR_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_GRANTED);
+    public void getPermissionGrantState_sensorPermission_notAbleToSetState_alsoCantReadState(
+            @SensorPermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
-                // TODO(b/204041462): Replace granting the permission here with the user pressing the
-                //  "deny" button on the permission
+            sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+            // TODO(b/204041462): Replace granting the permission here with the user pressing the
+            //  "deny" button on the permission
 
-                assertWithMessage("Should not be able to read permission grant state but can")
-                        .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission))
-                        .isEqualTo(PERMISSION_GRANT_STATE_DEFAULT);
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-                sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
-            }
+            assertWithMessage("Should not be able to read permission grant state but can")
+                    .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission))
+                    .isEqualTo(PERMISSION_GRANT_STATE_DEFAULT);
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
+            sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
         }
     }
 
     @Test
     @CanSetPolicyTest(policy = SetPermissionGrantState.class)
-    public void denyPermission_setsGrantState() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : DENYABLE_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
+    public void denyPermission_setsGrantState(@DeniablePermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
 
-            try {
-                boolean wasSet = sDeviceState.dpc().devicePolicyManager()
-                        .setPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission, PERMISSION_GRANT_STATE_DENIED);
+        try {
+            boolean wasSet = sDeviceState.dpc().devicePolicyManager()
+                    .setPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission, PERMISSION_GRANT_STATE_DENIED);
 
-                assertWithMessage("setPermissionGrantState did not return true")
-                        .that(wasSet).isTrue();
-                assertWithMessage("Permission grant state should be set to denied but was not")
-                        .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission))
-                        .isEqualTo(PERMISSION_GRANT_STATE_DENIED);
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertWithMessage("setPermissionGrantState did not return true")
+                    .that(wasSet).isTrue();
+            assertWithMessage("Permission grant state should be set to denied but was not")
+                    .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission))
+                    .isEqualTo(PERMISSION_GRANT_STATE_DENIED);
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
@@ -246,28 +259,26 @@
 
     @Test
     @PositivePolicyTest(policy = SetPermissionGrantState.class)
-    public void denyPermission_permissionIsDenied() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : DENYABLE_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_DENIED);
+    public void denyPermission_permissionIsDenied(
+            @DeniablePermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_DENIED);
 
-                assertWithMessage("Permission should not be granted but was").that(
-                        sTestApp.pkg().hasPermission(permission)).isFalse();
+            assertWithMessage("Permission should not be granted but was").that(
+                    sTestApp.pkg().hasPermission(permission)).isFalse();
 
-                // TODO(b/204041462): Test that the app cannot request the permission
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-                sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
-            }
+            // TODO(b/204041462): Test that the app cannot request the permission
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
+            sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
         }
     }
 
@@ -297,41 +308,37 @@
 
     @Test
     @NegativePolicyTest(policy = SetPermissionGrantState.class)
-    public void denyPermission_doesNotApply_permissionIsNotDenied() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : DENYABLE_PERMISSIONS) {
-            try {
-                sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
+    public void denyPermission_doesNotApply_permissionIsNotDenied(
+            @DeniablePermissionTestParameter String permission) {
+        try {
+            sTestApp.pkg().grantPermission(TestApis.users().instrumented(), permission);
 
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_DENIED);
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_DENIED);
 
-                assertWithMessage("Permission should not be denied but was").that(
-                        sTestApp.pkg().hasPermission(permission)).isTrue();
-            } finally {
-                sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
-            }
+            assertWithMessage("Permission should not be denied but was").that(
+                    sTestApp.pkg().hasPermission(permission)).isTrue();
+        } finally {
+            sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
         }
     }
 
     @Test
     @NegativePolicyTest(policy = SetPermissionGrantState.class)
-    public void grantPermission_doesNotApply_permissionIsNotGranted() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : DENYABLE_PERMISSIONS) {
-            try {
-                sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
+    public void grantPermission_doesNotApply_permissionIsNotGranted(
+            @DeniablePermissionTestParameter String permission) {
+        try {
+            sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
 
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_GRANTED);
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertWithMessage("Permission should not be granted but was").that(
-                        sTestApp.pkg().hasPermission(permission)).isFalse();
-            } finally {
-                sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
-            }
+            assertWithMessage("Permission should not be granted but was").that(
+                    sTestApp.pkg().hasPermission(permission)).isFalse();
+        } finally {
+            sTestApp.pkg().denyPermission(TestApis.users().instrumented(), permission);
         }
     }
 
@@ -442,37 +449,34 @@
     @Test
     @CanSetPolicyTest(policy = SetSensorPermissionGranted.class)
     @Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
-    public void grantSensorPermission_setsGrantState() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : SENSOR_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                boolean wasSet =
-                        sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission, PERMISSION_GRANT_STATE_GRANTED);
+    public void grantSensorPermission_setsGrantState(
+            @SensorPermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            boolean wasSet =
+                    sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertWithMessage("setPermissionGrantState did not return true")
-                        .that(wasSet).isTrue();
-                assertWithMessage("Permission grant state should be set to granted but was not")
-                        .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission))
-                        .isEqualTo(PERMISSION_GRANT_STATE_GRANTED);
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertWithMessage("setPermissionGrantState did not return true")
+                    .that(wasSet).isTrue();
+            assertWithMessage("Permission grant state should be set to granted but was not")
+                    .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission))
+                    .isEqualTo(PERMISSION_GRANT_STATE_GRANTED);
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
     @Test
     @PositivePolicyTest(policy = SetSmsPermissionGranted.class)
     public void grantSmsPermission_permissionIsGranted() {
-        // TODO(b/188893663): Replace with parameterization
         int existingGrantState = sDeviceState.dpc().devicePolicyManager()
                 .getPermissionGrantState(sDeviceState.dpc().componentName(),
                         sTestApp.packageName(), READ_SMS);
@@ -493,24 +497,22 @@
     @Test
     @PositivePolicyTest(policy = SetSensorPermissionGranted.class)
     @Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
-    public void grantSensorPermission_permissionIsGranted() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : SENSOR_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_GRANTED);
+    public void grantSensorPermission_permissionIsGranted(
+            @SensorPermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertWithMessage("Permission should be granted but was not").that(
-                        sTestApp.pkg().hasPermission(permission)).isTrue();
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertWithMessage("Permission should be granted but was not").that(
+                    sTestApp.pkg().hasPermission(permission)).isTrue();
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
@@ -537,24 +539,22 @@
     @Test
     @NegativePolicyTest(policy = SetSensorPermissionGranted.class)
     @Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
-    public void grantSensorPermission_doesNotApplyToUser_permissionIsNotGranted() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : SENSOR_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_GRANTED);
+    public void grantSensorPermission_doesNotApplyToUser_permissionIsNotGranted(
+            @SensorPermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertWithMessage("Permission should not be granted but was").that(
-                        sTestApp.pkg().hasPermission(permission)).isFalse();
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertWithMessage("Permission should not be granted but was").that(
+                    sTestApp.pkg().hasPermission(permission)).isFalse();
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
@@ -595,30 +595,28 @@
 
     @Test
     @CannotSetPolicyTest(policy = SetSensorPermissionGranted.class)
-    public void grantSensorPermission_cannotBeApplied_returnsTrueButDoesNotSetGrantState() {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : SENSOR_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
-            try {
-                boolean wasSet =
-                        sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission, PERMISSION_GRANT_STATE_GRANTED);
+    public void grantSensorPermission_cannotBeApplied_returnsTrueButDoesNotSetGrantState(
+            @SensorPermissionTestParameter String permission) {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        try {
+            boolean wasSet =
+                    sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertWithMessage("setPermissionGrantState did not return true")
-                        .that(wasSet).isTrue();
-                assertWithMessage("Permission grant state should not be set to granted but was")
-                        .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
-                                sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                                permission))
-                        .isNotEqualTo(PERMISSION_GRANT_STATE_GRANTED);
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertWithMessage("setPermissionGrantState did not return true")
+                    .that(wasSet).isTrue();
+            assertWithMessage("Permission grant state should not be set to granted but was")
+                    .that(sDeviceState.dpc().devicePolicyManager().getPermissionGrantState(
+                            sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                            permission))
+                    .isNotEqualTo(PERMISSION_GRANT_STATE_GRANTED);
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
@@ -626,30 +624,28 @@
     @PositivePolicyTest(policy = SetSensorPermissionGranted.class)
     @NotificationsTest
     @Ignore("TODO(198280344): Re-enable when we can set sensor permissions using device owner")
-    public void grantLocationPermission_userNotified() throws Exception {
-        // TODO(b/188893663): Replace with parameterization
-        for (String permission : LOCATION_PERMISSIONS) {
-            int existingGrantState = sDeviceState.dpc().devicePolicyManager()
-                    .getPermissionGrantState(sDeviceState.dpc().componentName(),
-                            sTestApp.packageName(), permission);
+    public void grantLocationPermission_userNotified(
+            @LocationPermissionTestParameter String permission) throws Exception {
+        int existingGrantState = sDeviceState.dpc().devicePolicyManager()
+                .getPermissionGrantState(sDeviceState.dpc().componentName(),
+                        sTestApp.packageName(), permission);
+        sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                permission, PERMISSION_GRANT_STATE_DEFAULT);
+        try (NotificationListener notifications = TestApis.notifications().createListener()) {
             sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
                     sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                    permission, PERMISSION_GRANT_STATE_DEFAULT);
-            try (NotificationListener notifications = TestApis.notifications().createListener()) {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, PERMISSION_GRANT_STATE_GRANTED);
+                    permission, PERMISSION_GRANT_STATE_GRANTED);
 
-                assertThat(notifications.query()
-                        .wherePackageName().isEqualTo(PERMISSION_CONTROLLER_PACKAGE_NAME)
-                        .whereNotification().channelId().isEqualTo(
-                                AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)
-                ).wasPosted();
-            } finally {
-                sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
-                        sDeviceState.dpc().componentName(), sTestApp.packageName(),
-                        permission, existingGrantState);
-            }
+            assertThat(notifications.query()
+                    .wherePackageName().isEqualTo(PERMISSION_CONTROLLER_PACKAGE_NAME)
+                    .whereNotification().channelId().isEqualTo(
+                            AUTO_GRANTED_PERMISSIONS_CHANNEL_ID)
+            ).wasPosted();
+        } finally {
+            sDeviceState.dpc().devicePolicyManager().setPermissionGrantState(
+                    sDeviceState.dpc().componentName(), sTestApp.packageName(),
+                    permission, existingGrantState);
         }
     }
 
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
index c6558e8..6f21edc 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/ScreenCaptureDisabledTest.java
@@ -33,6 +33,7 @@
 
 import com.android.bedstead.harrier.BedsteadJUnit4;
 import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureScreenIsOn;
 import com.android.bedstead.harrier.annotations.Postsubmit;
 import com.android.bedstead.harrier.annotations.SlowApiTest;
 import com.android.bedstead.harrier.annotations.enterprise.CanSetPolicyTest;
@@ -74,7 +75,7 @@
     public void setUp() {
         mAdmin = sDeviceState.dpc().componentName();
         mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
-        //TODO(b/198593716) : Use TestApi to take screnshot instead of UiAutomation.
+        //TODO(b/198593716) : Use TestApi to take screenshot instead of UiAutomation.
         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mLocalDevicePolicyManager = ApplicationProvider.getApplicationContext().getSystemService(
                 DevicePolicyManager.class);
@@ -133,6 +134,7 @@
     @Test
     @NegativePolicyTest(policy = ScreenCaptureDisabled.class)
     @Postsubmit(reason = "new test")
+    @EnsureScreenIsOn
     public void setScreenCaptureDisabled_true_screenCaptureWorks() {
         mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, true);
 
@@ -143,6 +145,7 @@
     @PositivePolicyTest(policy = ScreenCaptureDisabled.class)
     @Postsubmit(reason = "new test")
     @SlowApiTest("Screenshot policy can take minutes to propagate")
+    @EnsureScreenIsOn
     public void setScreenCaptureDisabled_true_screenCaptureFails() {
         mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, true);
 
@@ -152,6 +155,7 @@
     @Test
     @PositivePolicyTest(policy = ScreenCaptureDisabled.class)
     @Postsubmit(reason = "new test")
+    @EnsureScreenIsOn
     public void setScreenCaptureDisabled_false_screenCaptureWorks() {
         mDevicePolicyManager.setScreenCaptureDisabled(mAdmin, false);
 
diff --git a/tests/framework/base/OWNERS b/tests/framework/base/OWNERS
index f204dab..ad4b98f 100644
--- a/tests/framework/base/OWNERS
+++ b/tests/framework/base/OWNERS
@@ -13,3 +13,9 @@
 
 # Suggestions
 tmfang@google.com
+
+# Locale
+pratyushmore@google.com
+goldmanj@google.com
+
+# Locale
\ No newline at end of file
diff --git a/tests/framework/base/locale/Android.bp b/tests/framework/base/locale/Android.bp
new file mode 100644
index 0000000..9ef1c9f
--- /dev/null
+++ b/tests/framework/base/locale/Android.bp
@@ -0,0 +1,50 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsLocaleManagerTestCases",
+    defaults: ["cts_defaults"],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "cts-wm-util",
+        "cts-locale-util",
+    ],
+
+    srcs: [
+        "InstallerApp/**/*.java",
+        "TestApp/**/*.java",
+        "src/**/*.java",
+    ],
+
+    sdk_version: "test_current",
+}
diff --git a/tests/framework/base/locale/AndroidManifest.xml b/tests/framework/base/locale/AndroidManifest.xml
new file mode 100644
index 0000000..6839e33
--- /dev/null
+++ b/tests/framework/base/locale/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.localemanager.cts">
+
+  <!-- The permissions below would be needed if tests were not using "adopt shell permissions" to
+       obtain the necessary privileged permissions. -->
+  <!-- uses-permission android:name="android.permission.CHANGE_CONFIGURATION" /-->
+  <!-- uses-permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES" /-->
+
+  <eat-comment/>
+
+  <application android:debuggable="true">
+    <uses-library android:name="android.test.runner"/>
+  </application>
+
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+      android:targetPackage="android.localemanager.cts"
+      android:label="CTS tests for android.localemanager">
+    <meta-data android:name="listener"
+        android:value="com.android.cts.runner.CtsTestRunListener"/>
+  </instrumentation>
+
+</manifest>
+
diff --git a/tests/framework/base/locale/AndroidTest.xml b/tests/framework/base/locale/AndroidTest.xml
new file mode 100644
index 0000000..59845c5
--- /dev/null
+++ b/tests/framework/base/locale/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?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 Locale Manager test cases">
+  <option name="test-suite-tag" value="cts" />
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <!--  LocaleManager APIs should work the same in any user. -->
+  <!--  While running with atest use 'user-type secondary_user'-->
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+  <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+  <option name="not-shardable" value="true" />
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsLocaleManagerTestCases.apk" />
+    <option name="test-file-name" value="CtsLocaleTestApp.apk" />
+    <option name="test-file-name" value="CtsLocaleInstallerApp.apk" />
+  </target_preparer>
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+    <option name="package" value="android.localemanager.cts" />
+    <option name="hidden-api-checks" value="false" />
+  </test>
+</configuration>
diff --git a/tests/framework/base/locale/InstallerApp/Android.bp b/tests/framework/base/locale/InstallerApp/Android.bp
new file mode 100644
index 0000000..c4ba522
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsLocaleInstallerApp",
+    defaults: ["cts_defaults"],
+
+    static_libs: [
+        "cts-locale-util",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    sdk_version: "system_current",
+}
diff --git a/tests/framework/base/locale/InstallerApp/AndroidManifest.xml b/tests/framework/base/locale/InstallerApp/AndroidManifest.xml
new file mode 100644
index 0000000..67f2d22
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.localemanager.cts.installer">
+  <application>
+    <uses-library android:name="android.test.runner"/>
+    <activity android:name=".MainActivity"
+              android:label="MainActivity"
+              android:exported="true" >
+      <intent-filter>
+        <action android:name="android.intent.action.MAIN"/>
+        <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+      </intent-filter>
+    </activity>
+    <receiver android:name="android.localemanager.cts.installer.InstallerBroadcastReceiver"  android:exported="true" android:enabled="true">
+      <intent-filter>
+        <action android:name="android.intent.action.APPLICATION_LOCALE_CHANGED"/>
+        <action android:name="any.action"/>
+      </intent-filter>
+    </receiver>
+  </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.java b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.java
new file mode 100644
index 0000000..98c5ab1
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/InstallerBroadcastReceiver.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 android.localemanager.cts.installer;
+
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A broadcast receiver that listens to
+ * {@link android.content.Intent#ACTION_APPLICATION_LOCALE_CHANGED}
+ * when it is the installer of the app whose locale has changed.
+ */
+public class InstallerBroadcastReceiver extends BroadcastReceiver{
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Upon successful receipt of broadcast, send back the response to the test which
+        // invoked the change, so that it can verify correctness.
+        if (Intent.ACTION_APPLICATION_LOCALE_CHANGED.equals(intent.getAction())) {
+            context.sendBroadcast(constructResultIntent(
+                    INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION, intent));
+        }
+    }
+}
diff --git a/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java
new file mode 100644
index 0000000..9e25206
--- /dev/null
+++ b/tests/framework/base/locale/InstallerApp/src/android/localemanager/cts/installer/MainActivity.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.localemanager.cts.installer;
+
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+import androidx.annotation.Nullable;
+
+/**
+ * An activity used by {@link LocaleManagerTests} to query locales for a given package
+ * when this app is set as the installer of the given package.
+ */
+public class MainActivity extends Activity {
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        if (intent != null && intent.hasExtra(EXTRA_QUERY_LOCALES)) {
+            LocaleManager localeManager = getSystemService(LocaleManager.class);
+            String packageName = intent.getStringExtra(EXTRA_QUERY_LOCALES);
+            LocaleList locales = localeManager.getApplicationLocales(packageName);
+            sendBroadcast(constructResultIntent(INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION,
+                    packageName, locales));
+            finish();
+        }
+    }
+}
diff --git a/tests/framework/base/locale/OWNERS b/tests/framework/base/locale/OWNERS
new file mode 100644
index 0000000..694a4ac
--- /dev/null
+++ b/tests/framework/base/locale/OWNERS
@@ -0,0 +1,3 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=1082762&template=1601534
+pratyushmore@google.com
+goldmanj@google.com
\ No newline at end of file
diff --git a/tests/framework/base/locale/TEST_MAPPING b/tests/framework/base/locale/TEST_MAPPING
new file mode 100644
index 0000000..7dd5884
--- /dev/null
+++ b/tests/framework/base/locale/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsLocaleManagerTestCases"
+    }
+  ]
+}
diff --git a/tests/framework/base/locale/TestApp/Android.bp b/tests/framework/base/locale/TestApp/Android.bp
new file mode 100644
index 0000000..db1c0bf
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/Android.bp
@@ -0,0 +1,42 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsLocaleTestApp",
+    defaults: ["cts_defaults"],
+
+    static_libs: [
+        "cts-locale-util",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    srcs: ["src/**/*.java"],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    sdk_version: "system_current",
+
+}
diff --git a/tests/framework/base/locale/TestApp/AndroidManifest.xml b/tests/framework/base/locale/TestApp/AndroidManifest.xml
new file mode 100644
index 0000000..cc3f17a
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.localemanager.cts.app">
+
+  <application>
+    <uses-library android:name="android.test.runner"/>
+
+    <activity android:name=".MainActivity"
+        android:label="MainActivity"
+        android:exported="true"
+        android:configChanges="locale|layoutDirection|screenLayout">
+      <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="any.action"/>
+      </intent-filter>
+    </activity>
+
+    <receiver android:name="android.localemanager.cts.app.TestBroadcastReceiver"  android:exported="true" android:enabled="true">
+      <intent-filter>
+        <action android:name="android.intent.action.LOCALE_CHANGED"/>
+      </intent-filter>
+    </receiver>
+  </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.java b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.java
new file mode 100644
index 0000000..5182515
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/MainActivity.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 android.localemanager.cts.app;
+
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_SET_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_PACKAGE;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.os.LocaleList;
+
+import androidx.annotation.Nullable;
+
+/**
+ * This app is used as an external package to test system api
+ * {@link LocaleManager#setApplicationLocales(String, LocaleList)}
+ *
+ * <p> This activity is invoked by the {@link LocaleManagerTests} for below reasons:
+ * <ul>
+ * <li> To keep the app in the foreground/background.
+ * <li> To query locales in the running activity on app restart.
+ * </ul>
+ */
+public class MainActivity extends Activity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        if (intent != null && intent.hasExtra(EXTRA_QUERY_LOCALES)) {
+            // This intent extra is sent by app for restarting the app.
+            // Upon app restart, we want to check that the correct locales are received.
+            // So fetch the locales and send them to the calling test for verification.
+            LocaleManager localeManager = getSystemService(LocaleManager.class);
+            LocaleList locales = localeManager.getApplicationLocales();
+
+            // Send back the response with package name of this app and the locales fetched
+            // in current activity to the calling test.
+            sendBroadcast(constructResultIntent(TEST_APP_CREATION_INFO_PROVIDER_ACTION,
+                    TEST_APP_PACKAGE, locales));
+            finish();
+        } else if (intent != null && intent.hasExtra(EXTRA_SET_LOCALES)) {
+            // The invoking test directed us to set our application locales to the specified value
+            LocaleManager localeManager = getSystemService(LocaleManager.class);
+            localeManager.setApplicationLocales(LocaleList.forLanguageTags(
+                    intent.getStringExtra(EXTRA_SET_LOCALES)));
+            finish();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        LocaleList locales = newConfig.getLocales();
+
+        // Send back the received locales to the test for correctness assertion
+        sendBroadcast(constructResultIntent(TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION,
+                TEST_APP_PACKAGE, locales));
+        finish();
+    }
+}
diff --git a/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java
new file mode 100644
index 0000000..a68d3af
--- /dev/null
+++ b/tests/framework/base/locale/TestApp/src/android/localemanager/cts/app/TestBroadcastReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.localemanager.cts.app;
+
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleUtils.constructResultIntent;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * A broadcast receiver that listens to {@link android.content.Intent#ACTION_LOCALE_CHANGED}
+ * when the locale for this app is changed by the test.
+ */
+public class TestBroadcastReceiver extends BroadcastReceiver{
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+
+        // Upon successful receipt of broadcast, send back the response to the test which invoked
+        // the change, so that it can verify correctness.
+        if (Intent.ACTION_LOCALE_CHANGED.equals(intent.getAction())) {
+            context.sendBroadcast(constructResultIntent(TEST_APP_BROADCAST_INFO_PROVIDER_ACTION,
+                    intent));
+        }
+    }
+}
diff --git a/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java b/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java
new file mode 100644
index 0000000..ee63fe2
--- /dev/null
+++ b/tests/framework/base/locale/src/android/localemanager/cts/BlockingBroadcastReceiver.java
@@ -0,0 +1,82 @@
+/*
+ * 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.localemanager.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.LocaleList;
+
+import org.junit.Assert;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Used to dynamically register a receiver in instrumentation test.
+ *
+ * <p>This is used to listen to the following:
+ * <ul>
+ * <li> Broadcasts sent to the current app instrumenting the test. The broadcast is sent by the
+ * service being tested.
+ * <li> Response sent by other apps(TestApp, InstallerApp) to the tests.
+ * </ul>
+ */
+public class BlockingBroadcastReceiver extends BroadcastReceiver {
+    private CountDownLatch mLatch = new CountDownLatch(1);
+    private String mPackageName;
+    private LocaleList mLocales;
+    private int mCalls;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (intent.hasExtra(Intent.EXTRA_PACKAGE_NAME)) {
+            mPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        }
+        if (intent.hasExtra(Intent.EXTRA_LOCALE_LIST)) {
+            mLocales = intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST);
+        }
+        mCalls += 1;
+        mLatch.countDown();
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public LocaleList getLocales() {
+        return mLocales;
+    }
+
+    public void await() throws Exception {
+        mLatch.await(5, TimeUnit.SECONDS);
+    }
+
+    public void reset() {
+        mLatch = new CountDownLatch(1);
+        mCalls = 0;
+        mPackageName = null;
+        mLocales = null;
+    }
+
+    /**
+     * Waits for a while and checks no broadcasts are received.
+     */
+    public void assertNoBroadcastReceived() throws Exception {
+        await();
+        Assert.assertEquals(0, mCalls);
+    }
+}
diff --git a/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java b/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java
new file mode 100644
index 0000000..3edcb16
--- /dev/null
+++ b/tests/framework/base/locale/src/android/localemanager/cts/LocaleManagerTests.java
@@ -0,0 +1,538 @@
+/*
+ * 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.localemanager.cts;
+
+import static android.localemanager.cts.util.LocaleConstants.CALLING_PACKAGE;
+import static android.localemanager.cts.util.LocaleConstants.DEFAULT_APP_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.DEFAULT_SYSTEM_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_QUERY_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.EXTRA_SET_LOCALES;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_BROADCAST_RECEIVER;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_APP_MAIN_ACTIVITY;
+import static android.localemanager.cts.util.LocaleConstants.INSTALLER_PACKAGE;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_BROADCAST_RECEIVER;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_CREATION_INFO_PROVIDER_ACTION;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_MAIN_ACTIVITY;
+import static android.localemanager.cts.util.LocaleConstants.TEST_APP_PACKAGE;
+import static android.server.wm.CliIntentExtra.extraString;
+
+import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.LocaleManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.LocaleList;
+import android.server.wm.ActivityManagerTestBase;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link android.app.LocaleManager} API(s).
+ *
+ * Build/Install/Run: atest CtsLocaleManagerTestCases
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleManagerTests extends ActivityManagerTestBase {
+    private Context mContext;
+    private LocaleManager mLocaleManager;
+
+    /* System locales that were set on the device prior to running tests */
+    private LocaleList mPreviousSystemLocales;
+
+    /* Receiver to listen to the broadcast in the calling (instrumentation) app. */
+    private BlockingBroadcastReceiver mCallingAppBroadcastReceiver;
+
+    /* Receiver to listen to the response from the test app's broadcast receiver. */
+    private BlockingBroadcastReceiver mTestAppBroadcastInfoProvider;
+
+    /* Receiver to listen to the response from the test app's activity. */
+    private BlockingBroadcastReceiver mTestAppCreationInfoProvider;
+
+    /* Receiver to listen to the response from the test app's onConfigChanged method. */
+    private BlockingBroadcastReceiver mTestAppConfigChangedInfoProvider;
+
+    /* Receiver to listen to the response from the installer app. */
+    private BlockingBroadcastReceiver mInstallerBroadcastInfoProvider;
+
+    /* Receiver to listen to the response from the installer app's activity. */
+    private BlockingBroadcastReceiver mInstallerAppCreationInfoProvider;
+
+    @Before
+    public void setUp() throws Exception {
+        // Unlocks the device if locked, since we have tests where the app/activity needs
+        // to be in the foreground/background.
+        super.setUp();
+
+        mContext = InstrumentationRegistry.getTargetContext();
+        mLocaleManager = mContext.getSystemService(LocaleManager.class);
+
+        // Set custom system locales for these tests.
+        // Store the existing system locales and reset back to it in tearDown.
+        mPreviousSystemLocales = mLocaleManager.getSystemLocales();
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setSystemLocales(DEFAULT_SYSTEM_LOCALES),
+                Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
+
+        resetAppLocalesAsEmpty();
+        AmUtils.waitForBroadcastIdle();
+
+        mCallingAppBroadcastReceiver = new BlockingBroadcastReceiver();
+        mTestAppBroadcastInfoProvider = new BlockingBroadcastReceiver();
+        mInstallerBroadcastInfoProvider = new BlockingBroadcastReceiver();
+        mTestAppCreationInfoProvider = new BlockingBroadcastReceiver();
+        mTestAppConfigChangedInfoProvider = new BlockingBroadcastReceiver();
+        mInstallerAppCreationInfoProvider = new BlockingBroadcastReceiver();
+
+        mContext.registerReceiver(mCallingAppBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        mContext.registerReceiver(mTestAppBroadcastInfoProvider,
+                new IntentFilter(TEST_APP_BROADCAST_INFO_PROVIDER_ACTION));
+        mContext.registerReceiver(mInstallerBroadcastInfoProvider,
+                new IntentFilter(INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION));
+        mContext.registerReceiver(mTestAppCreationInfoProvider,
+                new IntentFilter(TEST_APP_CREATION_INFO_PROVIDER_ACTION));
+        mContext.registerReceiver(mTestAppConfigChangedInfoProvider,
+                new IntentFilter(TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION));
+        mContext.registerReceiver(mInstallerAppCreationInfoProvider,
+                new IntentFilter(INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION));
+
+        setInstallerForPackage(CALLING_PACKAGE);
+        setInstallerForPackage(TEST_APP_PACKAGE);
+
+        bindToBroadcastReceiverOfApp(TEST_APP_PACKAGE, TEST_APP_BROADCAST_RECEIVER);
+        bindToBroadcastReceiverOfApp(INSTALLER_PACKAGE, INSTALLER_APP_BROADCAST_RECEIVER);
+
+        resetReceivers();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        unRegisterReceiver(mCallingAppBroadcastReceiver);
+        unRegisterReceiver(mTestAppBroadcastInfoProvider);
+        unRegisterReceiver(mInstallerBroadcastInfoProvider);
+        unRegisterReceiver(mTestAppCreationInfoProvider);
+        unRegisterReceiver(mInstallerAppCreationInfoProvider);
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setSystemLocales(mPreviousSystemLocales),
+                Manifest.permission.CHANGE_CONFIGURATION, Manifest.permission.WRITE_SETTINGS);
+    }
+
+    private void unRegisterReceiver(BlockingBroadcastReceiver receiver) {
+        if (receiver != null) {
+            mContext.unregisterReceiver(receiver);
+            receiver = null;
+        }
+    }
+
+    /**
+     * Send broadcast to given app's receiver with flag {@link Intent#FLAG_INCLUDE_STOPPED_PACKAGES}
+     * and wait for it to be received.
+     *
+     * <p/> This is required since app is in stopped state after installation by tradefed,
+     * and the receivers in another apps don't receive the broadcast without
+     * FLAG_INCLUDE_STOPPED_PACKAGES because they have never been opened even once
+     * after installation.
+     * By doing this we make sure that app is un-stopped when the system sends the broadcasts
+     * in the tests.
+     */
+    private void bindToBroadcastReceiverOfApp(String packageName, String broadcastReceiver) {
+        final Intent intent = new Intent()
+                .setComponent(new ComponentName(packageName, broadcastReceiver))
+                .setFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        CountDownLatch latch = new CountDownLatch(1);
+        mContext.sendOrderedBroadcast(
+                intent,
+                null /* receiverPermission */,
+                new BroadcastReceiver() { /* resultReceiver */
+                    @Override public void onReceive(Context context, Intent intent) {
+                        latch.countDown();
+                    }
+                },
+                null /* scheduler */,
+                Activity.RESULT_OK, /* initialCode */
+                null /* initialData */,
+                null /* initialExtras */);
+        try {
+            assertTrue("Timed out waiting for test broadcast to be received",
+                    latch.await(5_000, TimeUnit.MILLISECONDS));
+        } catch (InterruptedException e) {
+            throw new IllegalStateException("Interrupted", e);
+        }
+    }
+
+    /**
+     * Sets the installer as {@link #INSTALLER_PACKAGE} for the target package.
+     */
+    private void setInstallerForPackage(String targetPackageName) {
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mContext.getPackageManager(),
+                (pm) -> pm.setInstallerPackageName(targetPackageName, INSTALLER_PACKAGE));
+    }
+
+    /**
+     * Resets the countdown latch in all the receivers.
+     */
+    private void resetReceivers() {
+        mCallingAppBroadcastReceiver.reset();
+        mInstallerBroadcastInfoProvider.reset();
+        mTestAppBroadcastInfoProvider.reset();
+        mTestAppCreationInfoProvider.reset();
+        mTestAppConfigChangedInfoProvider.reset();
+        mInstallerAppCreationInfoProvider.reset();
+    }
+
+    @Test
+    public void testSetApplicationLocales_persistsAndSendsBroadcast() throws Exception {
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+        assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.await();
+        assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+                CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+
+        mInstallerBroadcastInfoProvider.await();
+        assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider,
+                CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+    }
+
+    @Test
+    public void testSetApplicationLocales_resetToEmptyLocales_persistsAndSendsBroadcast()
+            throws Exception {
+        // First set the locales to non-empty
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+        assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.await();
+        assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+                CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.reset();
+
+        mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+        assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
+        mCallingAppBroadcastReceiver.await();
+        assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+                CALLING_PACKAGE, LocaleList.getEmptyLocaleList());
+    }
+
+    @Test
+    public void testSetApplicationLocales_sameLocalesEmpty_noBroadcastSent() throws Exception {
+        // At the start of the test, no app-specific locales are set, so just try to set it to
+        //   empty again
+        mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+        assertLocalesCorrectlySetForCallingApp(LocaleList.getEmptyLocaleList());
+        mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+    }
+
+    @Test
+    public void testSetApplicationLocales_sameLocalesNonEmpty_noBroadcastSent() throws Exception {
+        // First set the locales to non-empty
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+        assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.await();
+        assertReceivedBroadcastContains(mCallingAppBroadcastReceiver,
+                CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.reset();
+
+        // Then reset to the same app-specific locales, and verify no broadcasts are sent
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+        assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+        mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+    }
+
+    @Test
+    public void testSetApplicationLocales_getDefaultLocaleList_returnsCorrectList()
+            throws Exception {
+        // Fetch the current system locales when there are no app-locales set.
+        LocaleList systemLocales = LocaleList.getDefault();
+        assertEquals(DEFAULT_SYSTEM_LOCALES, systemLocales);
+
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+        // Wait for a while since LocaleList::getDefault could take a while to
+        // reflect the new app locales.
+        mCallingAppBroadcastReceiver.await();
+        assertEquals(combineLocales(DEFAULT_APP_LOCALES, systemLocales), LocaleList.getDefault());
+    }
+
+    @Test
+    public void testSetApplicationLocales_forAnotherAppInForeground_persistsAndSendsBroadcast()
+            throws Exception {
+        // Bring the TestApp to the foreground by invoking an activity and verify its visibility.
+        launchActivity(TEST_APP_MAIN_ACTIVITY);
+        mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
+
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+                Manifest.permission.CHANGE_CONFIGURATION);
+        assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+        mTestAppBroadcastInfoProvider.await();
+        assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+
+        mInstallerBroadcastInfoProvider.await();
+        assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+    }
+
+    @Test
+    public void testSetApplicationLocales_forAnotherAppInBackground_persistsAndSendsBroadcast()
+            throws Exception {
+        // Invoke the app by launching an activity.
+        launchActivity(TEST_APP_MAIN_ACTIVITY);
+        // Send the TestApp to the background.
+        launchHomeActivity();
+        mWmState.waitAndAssertVisibilityGone(TEST_APP_MAIN_ACTIVITY);
+
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+                Manifest.permission.CHANGE_CONFIGURATION);
+
+        assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+        mTestAppBroadcastInfoProvider.await();
+        assertReceivedBroadcastContains(mTestAppBroadcastInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+
+        mInstallerBroadcastInfoProvider.await();
+        assertReceivedBroadcastContains(mInstallerBroadcastInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+    }
+
+    @Test
+    public void testSetApplicationLocales_forAnotherApp_persistsOnAppRestart() throws Exception {
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+                Manifest.permission.CHANGE_CONFIGURATION);
+
+        // Re-start the app by starting an activity and check if locales correctly
+        // received by the app and listen to the broadcast for result from the app.
+        launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_QUERY_LOCALES, "true"));
+
+        mTestAppCreationInfoProvider.await();
+        assertReceivedBroadcastContains(mTestAppCreationInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+    }
+
+    @Test
+    public void
+            testSetApplicationLocales_wthoutPermissionforAnotherApp_throwsExceptionAndNoBroadcast()
+            throws Exception {
+        try {
+            mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+        } catch (SecurityException e) {
+            // expected as not having appropriate permission to change locales for another app.
+        }
+
+        // Since the locales weren't allowed to persist, no broadcasts should be sent by the system.
+        mTestAppBroadcastInfoProvider.assertNoBroadcastReceived();
+        mInstallerBroadcastInfoProvider.assertNoBroadcastReceived();
+    }
+
+    @Test
+    public void testSetApplicationLocales_forAnotherAppInForeground_callsOnConfigChanged()
+            throws Exception {
+        // Bring the TestApp to the foreground by invoking an activity and verify its visibility.
+        launchActivity(TEST_APP_MAIN_ACTIVITY);
+        mWmState.assertVisibility(TEST_APP_MAIN_ACTIVITY, /* visible*/ true);
+
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES),
+                Manifest.permission.CHANGE_CONFIGURATION);
+        assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+        mTestAppConfigChangedInfoProvider.await();
+        assertReceivedBroadcastContains(mTestAppConfigChangedInfoProvider, TEST_APP_PACKAGE,
+                DEFAULT_APP_LOCALES);
+    }
+
+    @Test
+    public void testSetApplicationLocales_withReadAppSpecificLocalesPermission_receivesBroadcast()
+            throws Exception {
+
+        BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+                new BlockingBroadcastReceiver();
+        mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+        // Hold Manifest.permission.READ_APP_SPECIFIC_LOCALES while the broadcast is sent,
+        //   so that we receive it
+        runWithShellPermissionIdentity(() -> {
+            // Tell the test app to change its app-specific locales
+            launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
+                    DEFAULT_APP_LOCALES.toLanguageTags()));
+            assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+            appSpecificLocaleBroadcastReceiver.await();
+        }, Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+        assertReceivedBroadcastContains(appSpecificLocaleBroadcastReceiver,
+                TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+    }
+
+
+    @Test
+    public void testSetApplicationLocales_appWithoutPermission_noBroadcastsReceived()
+            throws Exception {
+
+        BlockingBroadcastReceiver appSpecificLocaleBroadcastReceiver =
+                new BlockingBroadcastReceiver();
+        mContext.registerReceiver(appSpecificLocaleBroadcastReceiver,
+                new IntentFilter(Intent.ACTION_APPLICATION_LOCALE_CHANGED));
+
+        // Tell the test app to change its app-specific locales
+        launchActivity(TEST_APP_MAIN_ACTIVITY, extraString(EXTRA_SET_LOCALES,
+                DEFAULT_APP_LOCALES.toLanguageTags()));
+        assertLocalesCorrectlySetForAnotherApp(TEST_APP_PACKAGE, DEFAULT_APP_LOCALES);
+
+        // Ensure that no broadcasts were received since the change was for another app (the Test
+        //   App) and we are neither the Test App's installer, nor do we hold
+        //   Manifest.permission.READ_APP_SPECIFIC_LOCALES
+        appSpecificLocaleBroadcastReceiver.assertNoBroadcastReceived();
+        mCallingAppBroadcastReceiver.assertNoBroadcastReceived();
+    }
+
+    @Test
+    public void testGetApplicationLocales_callerIsInstaller_returnsLocales() throws Exception {
+        // Set locales for calling app
+        mLocaleManager.setApplicationLocales(DEFAULT_APP_LOCALES);
+
+        // Make sure that locales were set for the app.
+        assertLocalesCorrectlySetForCallingApp(DEFAULT_APP_LOCALES);
+        // Tell the installer app to fetch locales for calling package.
+        launchActivity(INSTALLER_APP_MAIN_ACTIVITY,
+                extraString(EXTRA_QUERY_LOCALES, CALLING_PACKAGE));
+
+        mInstallerAppCreationInfoProvider.await();
+        assertReceivedBroadcastContains(mInstallerAppCreationInfoProvider,
+                CALLING_PACKAGE, DEFAULT_APP_LOCALES);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testGetApplicationLocales_withoutPermissionforAnotherApp_throwsException()
+            throws Exception {
+        mLocaleManager.getApplicationLocales(TEST_APP_PACKAGE);
+        fail("Expected SecurityException due to not having appropriate permission "
+                + "for querying locales of another app.");
+    }
+
+    @Test
+    public void testGetApplicationLocales_noAppLocalesSet_returnsEmptyList() {
+        // When app-specific locales aren't set, we expect get api to return empty list
+        // and not throw any error.
+        assertEquals(LocaleList.getEmptyLocaleList(), mLocaleManager.getApplicationLocales());
+    }
+
+    /**
+     * Verifies that the locales are correctly set for calling(instrumentation) app
+     * by fetching locales of the app with a binder call.
+     */
+    private void assertLocalesCorrectlySetForCallingApp(LocaleList expectedLocales) {
+        assertEquals(expectedLocales, mLocaleManager.getApplicationLocales());
+    }
+
+    /**
+     * Verifies that the locales are correctly set for another package
+     * by fetching locales of the app with a binder call.
+     */
+    private void assertLocalesCorrectlySetForAnotherApp(String packageName,
+            LocaleList expectedLocales) throws Exception {
+        assertEquals(expectedLocales, getApplicationLocales(packageName));
+    }
+
+    private LocaleList getApplicationLocales(String packageName) throws Exception {
+        return callWithShellPermissionIdentity(() ->
+                mLocaleManager.getApplicationLocales(packageName),
+                Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+    }
+
+    /**
+     * Verifies that the broadcast received in the relevant apps have the correct information
+     * in the intent extras. It verifies the below extras:
+     * <ul>
+     * <li> {@link Intent#EXTRA_PACKAGE_NAME}
+     * <li> {@link Intent#EXTRA_LOCALE_LIST}
+     * </ul>
+     */
+    private void assertReceivedBroadcastContains(BlockingBroadcastReceiver receiver,
+            String expectedPackageName, LocaleList expectedLocales) {
+        assertEquals(expectedPackageName, receiver.getPackageName());
+        assertEquals(expectedLocales, receiver.getLocales());
+    }
+
+    /**
+     * Creates a combined {@link LocaleList} by placing app locales before system locales and
+     * dropping any duplicates.
+     *
+     * We need to combine them since {@link LocaleList} does not have any direct way like
+     * a normal list to check if locale or subset of locales is present.
+     */
+    private  LocaleList combineLocales(LocaleList appLocales, LocaleList systemLocales) {
+        Locale[] combinedLocales = new Locale[appLocales.size() + systemLocales.size()];
+        for (int i = 0; i < appLocales.size(); i++) {
+            combinedLocales[i] = appLocales.get(i);
+        }
+        for (int i = 0; i < systemLocales.size(); i++) {
+            combinedLocales[i + appLocales.size()] = systemLocales.get(i);
+        }
+        // Constructor of {@link LocaleList} removes duplicates.
+        return new LocaleList(combinedLocales);
+    }
+
+    /**
+     * Reset the locales for the apps to empty list as they could have been set previously
+     * by some other test.
+     */
+    private void resetAppLocalesAsEmpty() throws Exception {
+        // Reset locales for the calling app.
+        mLocaleManager.setApplicationLocales(LocaleList.getEmptyLocaleList());
+
+        // Reset locales for the TestApp.
+        runWithShellPermissionIdentity(() ->
+                        mLocaleManager.setApplicationLocales(TEST_APP_PACKAGE,
+                                LocaleList.getEmptyLocaleList()),
+                Manifest.permission.CHANGE_CONFIGURATION);
+    }
+}
diff --git a/tests/framework/base/locale/util/Android.bp b/tests/framework/base/locale/util/Android.bp
new file mode 100644
index 0000000..979454d
--- /dev/null
+++ b/tests/framework/base/locale/util/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+    name: "cts-locale-util",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+    ],
+}
diff --git a/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java
new file mode 100644
index 0000000..1c79f04
--- /dev/null
+++ b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleConstants.java
@@ -0,0 +1,72 @@
+/*
+ * 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.localemanager.cts.util;
+
+import android.content.ComponentName;
+import android.os.LocaleList;
+
+/**
+ * Common constants used across {@link android.localemanager.cts.LocaleManagerTests}
+ * and the test apps.
+ */
+public final class LocaleConstants {
+
+    private LocaleConstants() {}
+
+    public static final LocaleList DEFAULT_SYSTEM_LOCALES =
+            LocaleList.forLanguageTags("en_US,fr_FR");
+
+    public static final LocaleList DEFAULT_APP_LOCALES = LocaleList.forLanguageTags("hi,fr_FR");
+
+    public static final String CALLING_PACKAGE = "android.localemanager.cts";
+
+    public static final String TEST_APP_PACKAGE = "android.localemanager.cts.app";
+
+    public static final String INSTALLER_PACKAGE = "android.localemanager.cts.installer";
+
+    public static final ComponentName TEST_APP_MAIN_ACTIVITY = new ComponentName(TEST_APP_PACKAGE,
+            TEST_APP_PACKAGE + ".MainActivity");
+
+    public static final ComponentName INSTALLER_APP_MAIN_ACTIVITY =
+            new ComponentName(INSTALLER_PACKAGE, INSTALLER_PACKAGE + ".MainActivity");
+
+    public static final String TEST_APP_BROADCAST_INFO_PROVIDER_ACTION =
+            "android.locale.cts.action.TEST_APP_BROADCAST_INFO_PROVIDER";
+
+    public static final String TEST_APP_CREATION_INFO_PROVIDER_ACTION =
+            "android.locale.cts.action.TEST_APP_CREATION_INFO_PROVIDER";
+
+    public static final String TEST_APP_CONFIG_CHANGED_INFO_PROVIDER_ACTION =
+            "android.locale.cts.action.TEST_APP_CONFIG_CHANGED_INFO_PROVIDER";
+
+    public static final String INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION =
+            "android.locale.cts.action.INSTALLER_APP_BROADCAST_INFO_PROVIDER";
+
+    public static final String INSTALLER_APP_CREATION_INFO_PROVIDER_ACTION =
+            "android.locale.cts.action.INSTALLER_APP_CREATION_INFO_PROVIDER";
+
+    public static final String TEST_APP_BROADCAST_RECEIVER = TEST_APP_PACKAGE
+            + ".TestBroadcastReceiver";
+
+    public static final String INSTALLER_APP_BROADCAST_RECEIVER = INSTALLER_PACKAGE
+            + ".InstallerBroadcastReceiver";
+
+    public static final String EXTRA_QUERY_LOCALES = "query_locales";
+
+    public static final String EXTRA_SET_LOCALES = "set_locales";
+
+}
diff --git a/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.java b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.java
new file mode 100644
index 0000000..58c556e
--- /dev/null
+++ b/tests/framework/base/locale/util/src/android/localemanager/cts/util/LocaleUtils.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.localemanager.cts.util;
+
+import android.content.Intent;
+import android.os.LocaleList;
+import android.os.Parcelable;
+
+/**
+ * Static utility methods used across the test apps.
+ */
+public final class LocaleUtils {
+
+    private LocaleUtils() {}
+
+    /**
+     * Constructs a new intent with given action. Retrieves information from the passed intent
+     * and puts it in the extras of the new returned intent.
+     *
+     * <p> The passed intent contains package name and the locales. The intent action is expected to
+     * be either of the following types.
+     * <ul>
+     * <li> {@link LocaleConstants#TEST_APP_BROADCAST_INFO_PROVIDER_ACTION}
+     * <li> {@link LocaleConstants#INSTALLER_APP_BROADCAST_INFO_PROVIDER_ACTION}
+     * </ul>
+     *
+     */
+    public static Intent constructResultIntent(String action, Intent intent) {
+        return new Intent(action)
+                .putExtra(Intent.EXTRA_PACKAGE_NAME,
+                        intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME))
+                .putExtra(Intent.EXTRA_LOCALE_LIST,
+                            (Parcelable) intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST));
+    }
+
+    /**
+     * Constructs a new intent with given action. Also puts the given package name and the locales
+     * in the extras of the intent.
+     *
+     * <p> The action is expected to be of the type
+     * {@link LocaleConstants#TEST_APP_CREATION_INFO_PROVIDER_ACTION}
+     */
+    public static Intent constructResultIntent(String action, String packageName,
+            LocaleList locales) {
+        return new Intent(action)
+                .putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+                .putExtra(Intent.EXTRA_LOCALE_LIST, locales);
+    }
+}
diff --git a/tests/framework/base/localeconfig/Android.bp b/tests/framework/base/localeconfig/Android.bp
new file mode 100644
index 0000000..9e14b8b
--- /dev/null
+++ b/tests/framework/base/localeconfig/Android.bp
@@ -0,0 +1,46 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsLocaleConfigTestCases",
+    defaults: ["cts_defaults"],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    static_libs: [
+        "ctstestrunner-axt",
+        "junit",
+    ],
+
+    java_resource_dirs: ["res"],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    sdk_version: "test_current",
+}
+
diff --git a/tests/framework/base/localeconfig/AndroidManifest.xml b/tests/framework/base/localeconfig/AndroidManifest.xml
new file mode 100644
index 0000000..14159b9
--- /dev/null
+++ b/tests/framework/base/localeconfig/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.localeconfig">
+  <application android:debuggable="true"
+      android:localeConfig="@xml/locales_config">
+  </application>
+
+  <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+      android:targetPackage="com.android.cts.localeconfig"
+      android:label="CTS tests for android.app.LocaleConfig">
+    <meta-data android:name="listener"
+        android:value="com.android.cts.runner.CtsTestRunListener"/>
+  </instrumentation>
+</manifest>
diff --git a/tests/framework/base/localeconfig/AndroidTest.xml b/tests/framework/base/localeconfig/AndroidTest.xml
new file mode 100644
index 0000000..31c2b63
--- /dev/null
+++ b/tests/framework/base/localeconfig/AndroidTest.xml
@@ -0,0 +1,32 @@
+<?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 Locale Config test cases">
+  <option name="test-suite-tag" value="cts" />
+  <option name="config-descriptor:metadata" key="component" value="framework" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+  <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+    <option name="cleanup-apks" value="true" />
+    <option name="test-file-name" value="CtsLocaleConfigTestCases.apk" />
+    <option name="test-file-name" value="CtsNoLocaleConfigTagTests.apk" />
+    <option name="test-file-name" value="CtsMalformedInputTests.apk" />
+  </target_preparer>
+  <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+    <option name="package" value="com.android.cts.localeconfig" />
+    <option name="hidden-api-checks" value="false" />
+  </test>
+</configuration>
diff --git a/tests/framework/base/localeconfig/OWNERS b/tests/framework/base/localeconfig/OWNERS
new file mode 100644
index 0000000..248236f
--- /dev/null
+++ b/tests/framework/base/localeconfig/OWNERS
@@ -0,0 +1,4 @@
+# Bug template url: https://b.corp.google.com/issues/new?component=28174&template=1650608
+joshhou@google.com
+goldmanj@google.com
+pratyushmore@google.com
diff --git a/tests/framework/base/localeconfig/malformedinput/Android.bp b/tests/framework/base/localeconfig/malformedinput/Android.bp
new file mode 100644
index 0000000..0dbc3e7
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsMalformedInputTests",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    //resource_dirs: ["helper/res-malformedinput"],
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.legacy_legacy-support-v4"],
+    libs: ["android.test.base"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+            enabled: false,
+        },
+}
\ No newline at end of file
diff --git a/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml b/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml
new file mode 100644
index 0000000..84e0486
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.malformedinput">
+  <application android:debuggable="true"
+      android:localeConfig="@xml/locales_config">
+  </application>
+</manifest>
diff --git a/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml b/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml
new file mode 100644
index 0000000..0e77206
--- /dev/null
+++ b/tests/framework/base/localeconfig/malformedinput/res/xml/locales_config.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- the locale-config is misspelled -->
+<locale-confi xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
+	<locale android:name="en-US"/>
+	<locale android:name="zh-TW"/>
+	<locale android:name="pt"/>
+	<locale android:name="fr"/>
+	<locale android:name="zh-Hans-SG"/>
+</locale-confi>
+
diff --git a/tests/framework/base/localeconfig/nolocaleconfig/Android.bp b/tests/framework/base/localeconfig/nolocaleconfig/Android.bp
new file mode 100644
index 0000000..fcbe4c4
--- /dev/null
+++ b/tests/framework/base/localeconfig/nolocaleconfig/Android.bp
@@ -0,0 +1,34 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsNoLocaleConfigTagTests",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.legacy_legacy-support-v4"],
+    libs: ["android.test.base"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+    dex_preopt: {
+            enabled: false,
+        },
+}
\ No newline at end of file
diff --git a/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml b/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml
new file mode 100644
index 0000000..666f583
--- /dev/null
+++ b/tests/framework/base/localeconfig/nolocaleconfig/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT 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.nolocaleconfigtag">
+  <application android:debuggable="true">
+  </application>
+</manifest>
diff --git a/tests/framework/base/localeconfig/res/xml/locales_config.xml b/tests/framework/base/localeconfig/res/xml/locales_config.xml
new file mode 100644
index 0000000..9d68b32
--- /dev/null
+++ b/tests/framework/base/localeconfig/res/xml/locales_config.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<locale-config xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:aapt="http://schemas.android.com/aapt">
+	<locale android:name="en-US"/>
+	<locale android:name="zh-TW"/>
+	<locale android:name="pt"/>
+	<locale android:name="fr"/>
+	<locale android:name="zh-Hans-SG"/>
+</locale-config>
+
diff --git a/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java b/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java
new file mode 100644
index 0000000..8ec58fc
--- /dev/null
+++ b/tests/framework/base/localeconfig/src/com/android/cts/localeconfig/LocaleConfigTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES 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.localeconfig;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import android.app.LocaleConfig;
+import android.content.Context;
+import android.os.LocaleList;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Tests for {@link android.app.LocaleConfig} API(s).
+ *
+ * Build/Install/Run: atest LocaleConfigTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class LocaleConfigTest {
+    private static final String NOTAG_PACKAGE_NAME = "com.android.cts.nolocaleconfigtag";
+    private static final String MALFORMED_INPUT_PACKAGE_NAME = "com.android.cts.malformedinput";
+    private static final List<String> EXPECT_LOCALES = Arrays.asList(
+            new String[]{"en-US", "zh-TW", "pt", "fr", "zh-Hans-SG"});
+
+    @Test
+    public void testGetLocaleList() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        LocaleConfig localeConfig = new LocaleConfig(context);
+
+        assertEquals(EXPECT_LOCALES.stream()
+                .sorted()
+                .collect(Collectors.toList()),
+                new ArrayList<String>(Arrays.asList(
+                        localeConfig.getSupportedLocales().toLanguageTags().split(","))).stream()
+                .sorted()
+                .collect(Collectors.toList()));
+
+        assertEquals(LocaleConfig.STATUS_SUCCESS, localeConfig.getStatus());
+    }
+
+    @Test
+    public void testNoLocaleConfigTag() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        Context appContext = context.createPackageContext(NOTAG_PACKAGE_NAME, 0);
+        LocaleConfig localeConfig = new LocaleConfig(appContext);
+        LocaleList localeList = localeConfig.getSupportedLocales();
+
+        assertNull(localeList);
+
+        assertEquals(LocaleConfig.STATUS_NOT_SPECIFIED, localeConfig.getStatus());
+    }
+
+    @Test
+    public void testLocaleConfigMalformedInput() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        Context appContext = context.createPackageContext(MALFORMED_INPUT_PACKAGE_NAME, 0);
+        LocaleConfig localeConfig = new LocaleConfig(appContext);
+        LocaleList localeList = localeConfig.getSupportedLocales();
+
+        assertNull(localeList);
+
+        assertEquals(LocaleConfig.STATUS_PARSING_FAILED, localeConfig.getStatus());
+    }
+}
+
diff --git a/tests/framework/base/windowmanager/app/Android.bp b/tests/framework/base/windowmanager/app/Android.bp
index 94e915d..1288a70 100644
--- a/tests/framework/base/windowmanager/app/Android.bp
+++ b/tests/framework/base/windowmanager/app/Android.bp
@@ -26,8 +26,6 @@
 
     srcs: [
         "src/**/*.java",
-        // Used by InputMethodTestActivity for ImeAwareEditText.
-        ":compatibility-device-util-nodeps",
         ":CtsVerifierMockVrListenerServiceFiles",
     ],
 
diff --git a/tests/framework/base/windowmanager/app/AndroidManifest.xml b/tests/framework/base/windowmanager/app/AndroidManifest.xml
index fbbbbca..abeb1fe 100755
--- a/tests/framework/base/windowmanager/app/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/app/AndroidManifest.xml
@@ -610,6 +610,14 @@
              android:exported="true"
              android:colorMode="wideColorGamut"
              android:theme="@style/BadBlurryDialog"/>
+        <activity android:name=".ClearBackgroundTransitionExitActivity"
+            android:theme="@style/Theme.BackgroundColor"
+            android:exported="true"
+            android:colorMode="wideColorGamut"/>
+        <activity android:name=".ClearBackgroundTransitionEnterActivity"
+            android:theme="@style/Theme.BackgroundColor"
+            android:exported="true"
+            android:colorMode="wideColorGamut"/>
 
         <!-- Splash Screen Test Activities -->
         <activity android:name=".HandleSplashScreenExitActivity"
diff --git a/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml b/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml
new file mode 100644
index 0000000..4742a87
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/res/anim/show_background_hide_window_animation.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shareInterpolator="false"
+    android:showBackground="true">
+
+    <alpha
+        android:fromAlpha="0"
+        android:toAlpha="0"
+        android:fillEnabled="true"
+        android:fillBefore="true"
+        android:fillAfter="true"
+        android:interpolator="@android:interpolator/linear"
+        android:startOffset="0"
+        android:duration="5000"/>
+</set>
diff --git a/tests/framework/base/windowmanager/app/res/values/styles.xml b/tests/framework/base/windowmanager/app/res/values/styles.xml
index 0c9df63..611bc33 100644
--- a/tests/framework/base/windowmanager/app/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/app/res/values/styles.xml
@@ -92,4 +92,26 @@
     <style name="SplashScreenOverrideTheme" parent="ReplaceIconTheme">
         <item name="android:windowSplashScreenBackground">@drawable/red</item>
     </style>
+
+    <style name="Animation.ClearActivityAnimation" parent="@android:style/Animation.Activity">
+        <item name="android:activityOpenEnterAnimation">
+            @anim/show_background_hide_window_animation
+        </item>
+        <item name="android:activityOpenExitAnimation">
+            @anim/show_background_hide_window_animation
+        </item>
+        <item name="android:activityCloseEnterAnimation">
+            @anim/show_background_hide_window_animation
+        </item>
+        <item name="android:activityCloseExitAnimation">
+            @anim/show_background_hide_window_animation
+        </item>
+    </style>
+
+    <style name="Theme.BackgroundColor" parent="android:Theme.Light">
+        <item name="android:windowAnimationStyle">@style/Animation.ClearActivityAnimation</item>
+        <item name="android:background">#ffffff</item>
+        <item name="android:windowBackground">#ffffff</item>
+        <item name="android:colorBackground">#ffffff</item>
+    </style>
 </resources>
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
index 26c5b3a..130c970 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/BroadcastReceiverActivity.java
@@ -108,7 +108,8 @@
         }
 
         void register() {
-            mAppContext.registerReceiver(this, new IntentFilter(ACTION_TRIGGER_BROADCAST));
+            mAppContext.registerReceiver(this, new IntentFilter(ACTION_TRIGGER_BROADCAST),
+                    Context.RECEIVER_EXPORTED);
         }
 
         void associate(Activity activity) {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java
new file mode 100644
index 0000000..83ab042
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionEnterActivity.java
@@ -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.
+ */
+
+package android.server.wm.app;
+
+import android.app.Activity;
+
+/**
+ * Activity to test that show background for activity transitions works
+ */
+public class ClearBackgroundTransitionEnterActivity extends Activity {
+    @Override
+    protected void onResume() {
+        super.onResume();
+        setContentView(R.layout.background_image);
+        overridePendingTransition(R.anim.show_background_hide_window_animation,
+                R.anim.show_background_hide_window_animation);
+    }
+}
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java
new file mode 100644
index 0000000..be0efd0
--- /dev/null
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/ClearBackgroundTransitionExitActivity.java
@@ -0,0 +1,58 @@
+/*
+ * 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 static android.server.wm.app.Components.BackgroundActivityTransition.TRANSITION_REQUESTED;
+import static android.server.wm.app.Components.CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.server.wm.TestJournalProvider;
+
+/**
+ * Activity to test that show background for activity transitions works
+ */
+public class ClearBackgroundTransitionExitActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.background_image);
+
+        // Delay the starting the activity so we don't skip the transition.
+        startActivityDelayed();
+    }
+
+    private void startActivityDelayed() {
+        Runnable r = () -> {
+            // Notify the test journal that we are starting the activity transition
+            TestJournalProvider.putExtras(
+                    getBaseContext(), CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY, bundle -> {
+                        bundle.putBoolean(TRANSITION_REQUESTED,
+                                true);
+                    });
+            startActivity(new Intent(
+                    ClearBackgroundTransitionExitActivity.this,
+                    ClearBackgroundTransitionEnterActivity.class
+            ));
+        };
+
+        Handler h = new Handler();
+        h.postDelayed(r, 1000);
+    }
+}
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 79499f7..ff70b3d 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
@@ -278,6 +278,12 @@
     public static final ComponentName BAD_BLUR_ACTIVITY =
             component("BadBlurActivity");
 
+    public static final ComponentName CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY =
+            component("ClearBackgroundTransitionExitActivity");
+
+    public static final ComponentName CLEAR_BACKGROUND_TRANSITION_ENTER_ACTIVITY =
+            component("ClearBackgroundTransitionExitActivity");
+
     /**
      * The keys are used for {@link TestJournalProvider} when testing starting window.
      */
@@ -335,6 +341,7 @@
         public static final String COMMAND_NAVIGATE_UP_TO = "navigate_up_to";
         public static final String COMMAND_START_ACTIVITY = "start_activity";
         public static final String COMMAND_START_ACTIVITIES = "start_activities";
+        public static final String EXTRA_OPTION = "option";
     }
 
     /**
@@ -638,6 +645,10 @@
         public static final String SHOULD_HIDE = "should_hide";
     }
 
+    public static class BackgroundActivityTransition {
+        public static final String TRANSITION_REQUESTED = "transition_requested";
+    }
+
     private static ComponentName component(String className) {
         return component(Components.class, className);
     }
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
index c8e2140..b6be23b 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HandleSplashScreenExitActivity.java
@@ -38,7 +38,6 @@
 import android.graphics.drawable.Drawable;
 import android.os.Bundle;
 import android.server.wm.TestJournalProvider;
-import android.util.Log;
 import android.window.SplashScreen;
 
 public class HandleSplashScreenExitActivity extends Activity {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
index 4014999..0be70e1 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/HideOverlayWindowsActivity.java
@@ -37,7 +37,7 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION));
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION), Context.RECEIVER_EXPORTED);
     }
 
     BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index b39b09c..e053322 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -240,7 +240,7 @@
         filter.addAction(ACTION_SET_REQUESTED_ORIENTATION);
         filter.addAction(ACTION_FINISH);
         filter.addAction(ACTION_ON_PIP_REQUESTED);
-        registerReceiver(mReceiver, filter);
+        registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
 
         // Don't dump configuration when entering PIP to avoid the verifier getting the intermediate
         // state. In this case it is expected that the verifier will check the changed configuration
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
index 9c66d92..e087647 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/SplashScreenReplaceIconActivity.java
@@ -31,7 +31,6 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.server.wm.TestJournalProvider;
-import android.util.Log;
 import android.view.SurfaceView;
 import android.view.View;
 import android.window.SplashScreen;
diff --git a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
index aa16584..409bff9 100644
--- a/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
+++ b/tests/framework/base/windowmanager/appSecondUid/src/android/server/wm/second/EmbeddingActivity.java
@@ -40,7 +40,7 @@
         super.onCreate(icicle);
 
         IntentFilter broadcastFilter = new IntentFilter(ACTION_EMBEDDING_TEST_ACTIVITY_START);
-        registerReceiver(mBroadcastReceiver, broadcastFilter);
+        registerReceiver(mBroadcastReceiver, broadcastFilter, Context.RECEIVER_EXPORTED);
     }
 
     @Override
diff --git a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
index 6b30644..2a570ac 100644
--- a/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
+++ b/tests/framework/base/windowmanager/app_base/src/android/server/wm/app/TestActivity.java
@@ -17,14 +17,15 @@
 package android.server.wm.app;
 
 import static android.server.wm.app.Components.TestActivity.COMMAND_NAVIGATE_UP_TO;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITY;
 import static android.server.wm.app.Components.TestActivity.EXTRA_CONFIG_ASSETS_SEQ;
 import static android.server.wm.app.Components.TestActivity.EXTRA_FIXED_ORIENTATION;
-import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
 import static android.server.wm.app.Components.TestActivity.EXTRA_NO_IDLE;
+import static android.server.wm.app.Components.TestActivity.EXTRA_OPTION;
 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
-import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -88,7 +89,8 @@
     @Override
     protected void onStart() {
         super.onStart();
-        registerReceiver(mReceiver, new IntentFilter(TEST_ACTIVITY_ACTION_FINISH_SELF));
+        registerReceiver(mReceiver, new IntentFilter(TEST_ACTIVITY_ACTION_FINISH_SELF),
+                Context.RECEIVER_EXPORTED);
     }
 
     @Override
@@ -108,18 +110,19 @@
 
     @Override
     public void handleCommand(String command, Bundle data) {
+        final Bundle options = data.getParcelable(EXTRA_OPTION);
         switch (command) {
             case COMMAND_START_ACTIVITY:
                 final Intent startIntent = data.getParcelable(EXTRA_INTENT);
                 try {
-                    startActivity(startIntent);
+                    startActivity(startIntent, options);
                 } catch (Exception e) {
                     Log.w(getTag(), "Failed to startActivity: " + startIntent, e);
                 }
                 break;
             case COMMAND_START_ACTIVITIES:
                 final Parcelable[] intents = data.getParcelableArray(EXTRA_INTENTS);
-                startActivities(Arrays.copyOf(intents, intents.length, Intent[].class));
+                startActivities(Arrays.copyOf(intents, intents.length, Intent[].class), options);
                 break;
             case COMMAND_NAVIGATE_UP_TO:
                 final Intent intent = data.getParcelable(EXTRA_INTENT);
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
index d4de7b1..bc8038a 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/AndroidManifest.xml
@@ -25,6 +25,8 @@
         <receiver android:name=".StartBackgroundActivityReceiver"
              android:exported="true"/>
         <receiver android:name=".SendPendingIntentReceiver"
+                  android:exported="true"/>
+        <service android:name=".BackgroundActivityTestService"
              android:exported="true"/>
         <receiver android:name=".SimpleAdminReceiver"
              android:permission="android.permission.BIND_DEVICE_ADMIN"
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java
new file mode 100644
index 0000000..46ef78b
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/BackgroundActivityTestService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.backgroundactivity.appa;
+
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_START_ACTIVITY_RECEIVER;
+
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class BackgroundActivityTestService extends Service {
+    private final IBackgroundActivityTestService mBinder = new MyBinder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder.asBinder();
+    }
+
+    private class MyBinder extends IBackgroundActivityTestService.Stub {
+        @Override
+        public PendingIntent generatePendingIntent(boolean isBroadcast) {
+            if (isBroadcast) {
+                // Create a pendingIntent to launch send broadcast to appA and appA will start
+                // background activity.
+                Intent newIntent = new Intent();
+                newIntent.setComponent(APP_A_START_ACTIVITY_RECEIVER);
+                return PendingIntent.getBroadcast(BackgroundActivityTestService.this, 0, newIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            } else {
+                // Create a pendingIntent to launch appA's BackgroundActivity
+                Intent newIntent = new Intent();
+                newIntent.setComponent(APP_A_BACKGROUND_ACTIVITY);
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                return PendingIntent.getActivity(BackgroundActivityTestService.this, 0, newIntent,
+                        PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+            }
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
index 4f1dfcd..06eef12 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/Components.java
@@ -33,6 +33,8 @@
             component(Components.class, "StartBackgroundActivityReceiver");
     public static final ComponentName APP_A_SIMPLE_ADMIN_RECEIVER =
             component(Components.class, "SimpleAdminReceiver");
+    public static final ComponentName APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE =
+            component(Components.class, "BackgroundActivityTestService");
 
     /** Extra key constants for {@link #APP_A_FOREGROUND_ACTIVITY}. */
     public static class ForegroundActivity {
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
index 9f92875..b4e810a 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppA/src/android/server/wm/backgroundactivity/appa/ForegroundActivity.java
@@ -93,7 +93,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_LAUNCH_BACKGROUND_ACTIVITIES);
         filter.addAction(ACTION_FINISH_ACTIVITY);
-        registerReceiver(mReceiver, filter);
+        registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED);
     }
 
     @Override
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml b/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
index 4f1bed6..756827f 100755
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/AndroidManifest.xml
@@ -23,6 +23,9 @@
             android:name=".StartPendingIntentReceiver"
             android:exported="true"/>
         <activity
+            android:name=".StartPendingIntentActivity"
+            android:exported="true"/>
+        <activity
             android:name=".ForegroundActivity"
             android:exported="true" />
     </application>
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
index 9f72fbd..8f6cf1a 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/Components.java
@@ -23,9 +23,16 @@
 
     public static final ComponentName APP_B_FOREGROUND_ACTIVITY =
             component(Components.class, "ForegroundActivity");
+    public static final ComponentName APP_B_START_PENDING_INTENT_ACTIVITY =
+            component(Components.class, "StartPendingIntentActivity");
     public static final ComponentName APP_B_START_PENDING_INTENT_RECEIVER =
             component(Components.class, "StartPendingIntentReceiver");
 
+    /** Extra key constants for {@link #APP_B_START_PENDING_INTENT_ACTIVITY} */
+    public static class StartPendingIntentActivity {
+        public static final String ALLOW_BAL_EXTRA = "ALLOW_BAL_EXTRA";
+    }
+
     /** Extra key constants for {@link #APP_B_START_PENDING_INTENT_RECEIVER} */
     public static class StartPendingIntentReceiver {
         public static final String PENDING_INTENT_EXTRA = "PENDING_INTENT_EXTRA";
diff --git a/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java
new file mode 100644
index 0000000..c1e3f6d
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/AppB/src/android/server/wm/backgroundactivity/appb/StartPendingIntentActivity.java
@@ -0,0 +1,51 @@
+/*
+ * 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.backgroundactivity.appb;
+
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentActivity.ALLOW_BAL_EXTRA;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Receive pending intent from AppA and launch it
+ */
+public class StartPendingIntentActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle b) {
+        super.onCreate(b);
+        Intent intent = getIntent();
+        final PendingIntent pendingIntent = intent.getParcelableExtra(PENDING_INTENT_EXTRA);
+        final boolean allowBal = intent.getBooleanExtra(ALLOW_BAL_EXTRA, false);
+
+        try {
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setPendingIntentBackgroundActivityLaunchAllowed(allowBal);
+            Bundle bundle = options.toBundle();
+            pendingIntent.send(/* context */ null, /* code */0, /* intent */
+                    null, /* onFinished */null, /* handler */
+                    null, /* requiredPermission */ null, bundle);
+        } catch (PendingIntent.CanceledException e) {
+            throw new AssertionError(e);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
index 58843ed..2b54222 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/Android.bp
@@ -19,7 +19,7 @@
 java_test {
     name: "cts-background-activity-common",
 
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "aidl/**/*.aidl"],
 
     static_libs: [
         "androidx.annotation_annotation",
diff --git a/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl b/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl
new file mode 100644
index 0000000..405a290
--- /dev/null
+++ b/tests/framework/base/windowmanager/backgroundactivity/common/aidl/android/server/wm/backgroundactivity/appa/IBackgroundActivityTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.server.wm.backgroundactivity.appa;
+
+import android.app.PendingIntent;
+
+interface IBackgroundActivityTestService {
+    PendingIntent generatePendingIntent(boolean isBroadcast);
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
index e45e821..8ebac55 100644
--- a/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
+++ b/tests/framework/base/windowmanager/backgroundactivity/src/android/server/wm/BackgroundActivityLaunchTest.java
@@ -21,6 +21,7 @@
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
 import static android.server.wm.WindowManagerState.STATE_INITIALIZING;
 import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appa.Components.APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE;
 import static android.server.wm.backgroundactivity.appa.Components.APP_A_FOREGROUND_ACTIVITY;
 import static android.server.wm.backgroundactivity.appa.Components.APP_A_SECOND_BACKGROUND_ACTIVITY;
 import static android.server.wm.backgroundactivity.appa.Components.APP_A_SEND_PENDING_INTENT_RECEIVER;
@@ -37,6 +38,9 @@
 import static android.server.wm.backgroundactivity.appa.Components.SendPendingIntentReceiver.IS_BROADCAST_EXTRA;
 import static android.server.wm.backgroundactivity.appa.Components.StartBackgroundActivityReceiver.START_ACTIVITY_DELAY_MS_EXTRA;
 import static android.server.wm.backgroundactivity.appb.Components.APP_B_FOREGROUND_ACTIVITY;
+import static android.server.wm.backgroundactivity.appb.Components.APP_B_START_PENDING_INTENT_ACTIVITY;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentActivity.ALLOW_BAL_EXTRA;
+import static android.server.wm.backgroundactivity.appb.Components.StartPendingIntentReceiver.PENDING_INTENT_EXTRA;
 import static android.server.wm.backgroundactivity.common.CommonComponents.EVENT_NOTIFIER_EXTRA;
 
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
@@ -50,15 +54,20 @@
 import static org.junit.Assert.assertTrue;
 
 import android.Manifest;
+import android.app.PendingIntent;
 import android.app.UiAutomation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.os.IBinder;
 import android.os.ResultReceiver;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.SystemUserOnly;
+import android.server.wm.backgroundactivity.appa.IBackgroundActivityTestService;
 import android.server.wm.backgroundactivity.common.CommonComponents.Event;
 import android.server.wm.backgroundactivity.common.EventReceiver;
 
@@ -74,6 +83,8 @@
 import org.junit.Test;
 
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Stream;
 
@@ -113,6 +124,9 @@
      */
     private static final int BROADCAST_DELIVERY_TIMEOUT_MS = 60000;
 
+    private IBackgroundActivityTestService mBackgroundActivityTestService;
+    private ServiceConnection mBalServiceConnection;
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -145,6 +159,9 @@
         stopTestPackage(TEST_PACKAGE_APP_B);
         AppOpsUtils.reset(APP_A_PACKAGE_NAME);
         AppOpsUtils.reset(SHELL_PACKAGE);
+        if (mBalServiceConnection != null) {
+            mContext.unbindService(mBalServiceConnection);
+        }
     }
 
     @Test
@@ -415,6 +432,21 @@
     }
 
     @Test
+    public void testPendingIntentActivity_whenActivityAllowsBal_isNotBlocked() throws Exception {
+        startPendingIntentSenderActivity(true);
+        boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
+        assertTrue("Not able to launch background activity", result);
+        assertTaskStack(new ComponentName[]{APP_A_BACKGROUND_ACTIVITY}, APP_A_BACKGROUND_ACTIVITY);
+    }
+
+    @Test
+    public void testPendingIntentActivity_whenActivityDoesNotAllowBal_isBlocked() throws Exception {
+        startPendingIntentSenderActivity(false);
+        boolean result = waitForActivityFocused(APP_A_BACKGROUND_ACTIVITY);
+        assertFalse("Should not able to launch background activity", result);
+    }
+
+    @Test
     @FlakyTest(bugId = 130800326)
     public void testPendingIntentActivityNotBlocked_appAIsForeground() throws Exception {
         // Start AppA foreground activity
@@ -730,6 +762,49 @@
         return waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, componentName);
     }
 
+    private void setupPendingIntentService() throws Exception {
+        Intent bindIntent = new Intent();
+        bindIntent.setComponent(APP_A_BACKGROUND_ACTIVITY_TEST_SERVICE);
+        final CountDownLatch bindLatch = new CountDownLatch(1);
+
+        mBalServiceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                mBackgroundActivityTestService =
+                        IBackgroundActivityTestService.Stub.asInterface(service);
+                bindLatch.countDown();
+            }
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                mBackgroundActivityTestService = null;
+            }
+        };
+        boolean success = mContext.bindService(bindIntent, mBalServiceConnection,
+                Context.BIND_AUTO_CREATE);
+        assertTrue(success);
+        assertTrue("Timeout connecting to test service",
+                bindLatch.await(1000, TimeUnit.MILLISECONDS));
+    }
+
+    private void startPendingIntentSenderActivity(boolean allowBal) throws Exception {
+        setupPendingIntentService();
+        // Get a PendingIntent created by appA.
+        final PendingIntent pi;
+        try {
+            pi = mBackgroundActivityTestService.generatePendingIntent(false);
+        } catch (Exception e) {
+            throw new AssertionError(e);
+        }
+
+        // Start app B's activity so it runs send() on PendingIntent created by app A.
+        Intent secondIntent = new Intent();
+        secondIntent.setComponent(APP_B_START_PENDING_INTENT_ACTIVITY);
+        secondIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        secondIntent.putExtra(PENDING_INTENT_EXTRA, pi);
+        secondIntent.putExtra(ALLOW_BAL_EXTRA, allowBal);
+        mContext.startActivity(secondIntent);
+    }
+
     private void sendPendingIntentActivity() {
         Intent intent = new Intent();
         intent.setComponent(APP_A_SEND_PENDING_INTENT_RECEIVER);
diff --git a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
index ded84a7..30e99b0 100644
--- a/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
+++ b/tests/framework/base/windowmanager/overlayappbase/src/android/server/wm/overlay/ExitAnimationActivity.java
@@ -47,7 +47,8 @@
     protected void onStart() {
         super.onStart();
         registerReceiver(mReceiver,
-                new IntentFilter(Components.ExitAnimationActivityReceiver.ACTION_FINISH));
+                new IntentFilter(Components.ExitAnimationActivityReceiver.ACTION_FINISH),
+                Context.RECEIVER_EXPORTED);
     }
 
     @Override
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index 122ef2a..919eed8 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -21,8 +21,6 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
-import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.ActivityOptions;
@@ -53,30 +51,19 @@
  */
 @Presubmit
 public class ActivityTransitionTests extends ActivityManagerTestBase {
-    // See WindowManagerService.DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY
-    static final String DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY =
-            "persist.wm.disable_custom_task_animation";
-    static final boolean DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT = true;
-
     // Duration of the default wallpaper close animation
     static final long DEFAULT_ANIMATION_DURATION = 275L;
     // Duration of the R.anim.alpha animation
     static final long CUSTOM_ANIMATION_DURATION = 2000L;
 
-    private static boolean customTaskAnimationDisabled() {
-        try {
-            return Integer.parseInt(executeShellCommand(
-                    "getprop " + DISABLE_CUSTOM_TASK_ANIMATION_PROPERTY).replace("\n", "")) != 0;
-        } catch (NumberFormatException e) {
-            return DISABLE_CUSTOM_TASK_ANIMATION_DEFAULT;
-        }
-    }
+    // Allowable error for the measured animation duration.
+    static final long EXPECTED_DURATION_TOLERANCE_START = 200;
+    static final long EXPECTED_DURATION_TOLERANCE_FINISH = 1000;
 
-    @Test
+   @Test
     public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
-        final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
-        final long minDurationMs = expectedDurationMs;
-        final long maxDurationMs = expectedDurationMs + 300L;
+        final long minDurationMs = CUSTOM_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+        final long maxDurationMs = CUSTOM_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
         final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -114,51 +101,9 @@
     }
 
     @Test
-    public void testTaskTransitionDurationNoShortenAsExpected() throws Exception {
-        assumeFalse(customTaskAnimationDisabled());
-
-        final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
-        final long minDurationMs = expectedDurationMs;
-        final long maxDurationMs = expectedDurationMs + 300L;
-        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
-
-        final CountDownLatch latch = new CountDownLatch(1);
-        AtomicLong transitionStartTime = new AtomicLong();
-        AtomicLong transitionEndTime = new AtomicLong();
-
-        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
-            transitionStartTime.set(SystemClock.elapsedRealtime());
-        };
-
-        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
-            transitionEndTime.set(SystemClock.elapsedRealtime());
-            latch.countDown();
-        };
-
-        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
-                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
-                finishedListener).toBundle();
-        final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
-                .addFlags(FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(intent, bundle);
-        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
-        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
-                "Activity must be launched");
-
-        latch.await(2, TimeUnit.SECONDS);
-        final long totalTime = transitionEndTime.get() - transitionStartTime.get();
-        assertTrue("Actual transition duration should be in the range "
-                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
-                + "actual=" + totalTime, durationRange.contains(totalTime));
-    }
-
-    @Test
     public void testTaskTransitionOverrideDisabled() throws Exception {
-        assumeTrue(customTaskAnimationDisabled());
-
-        final long expectedDurationMs = DEFAULT_ANIMATION_DURATION - 100L;
-        final long minDurationMs = expectedDurationMs;
-        final long maxDurationMs = expectedDurationMs + 1000L;
+        final long minDurationMs = DEFAULT_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+        final long maxDurationMs = DEFAULT_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
         final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
 
         final CountDownLatch latch = new CountDownLatch(1);
@@ -195,11 +140,8 @@
 
     @Test
     public void testTaskTransitionOverride() throws Exception {
-        assumeTrue(customTaskAnimationDisabled());
-
-        final long expectedDurationMs = CUSTOM_ANIMATION_DURATION - 100L;
-        final long minDurationMs = expectedDurationMs;
-        final long maxDurationMs = expectedDurationMs + 1000L;
+        final long minDurationMs = CUSTOM_ANIMATION_DURATION - EXPECTED_DURATION_TOLERANCE_START;
+        final long maxDurationMs = CUSTOM_ANIMATION_DURATION + EXPECTED_DURATION_TOLERANCE_FINISH;
         final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
 
         final CountDownLatch latch = new CountDownLatch(1);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 401ebaf..74ef09e 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -24,7 +24,6 @@
 import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.UiDeviceUtils.pressBackButton;
 import static android.server.wm.UiDeviceUtils.pressHomeButton;
-import static android.server.wm.UiDeviceUtils.pressSleepButton;
 import static android.server.wm.VirtualDisplayHelper.waitForDefaultDisplayState;
 import static android.server.wm.WindowManagerState.STATE_RESUMED;
 import static android.server.wm.WindowManagerState.STATE_STOPPED;
@@ -327,9 +326,11 @@
         }
 
         getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
 
         getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME).execute();
 
         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
@@ -349,30 +350,27 @@
         }
         // Start LaunchingActivity and BroadcastReceiverActivity in two separate tasks.
         getLaunchActivityBuilder().setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                 .setIntentFlags(FLAG_ACTIVITY_NEW_TASK).execute();
         waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,"Activity must be resumed");
-        final int taskId1 = mWmState.getTaskByActivity(LAUNCHING_ACTIVITY).mTaskId;
-        final int taskId2 = mWmState.getTaskByActivity(BROADCAST_RECEIVER_ACTIVITY).mTaskId;
+        final int taskId = mWmState.getTaskByActivity(BROADCAST_RECEIVER_ACTIVITY).mTaskId;
 
         try {
-            runWithShellPermission(() -> {
-                mAtm.startSystemLockTaskMode(taskId1);
-                mAtm.startSystemLockTaskMode(taskId2);
-            });
+            runWithShellPermission(() -> mAtm.startSystemLockTaskMode(taskId));
             getLaunchActivityBuilder()
                     .setUseInstrumentation()
                     .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
+                    .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                     .setIntentFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME).execute();
-            waitAndAssertResumedActivity(BROADCAST_RECEIVER_ACTIVITY,"Activity must be resumed");
-            mBroadcastActionTrigger.finishBroadcastReceiverActivity();
-            mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY);
-
-            mWmState.assertHomeActivityVisible(false);
+            mWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
         } finally {
-            runWithShellPermission(() -> {
-                mAtm.stopSystemLockTaskMode();
-            });
+            runWithShellPermission(() -> mAtm.stopSystemLockTaskMode());
         }
+
+        mBroadcastActionTrigger.finishBroadcastReceiverActivity();
+        mWmState.waitAndAssertActivityRemoved(BROADCAST_RECEIVER_ACTIVITY);
+
+        mWmState.assertHomeActivityVisible(false);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
new file mode 100644
index 0000000..d85a255
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AnimationBackgroundTests.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BackgroundActivityTransition.TRANSITION_REQUESTED;
+import static android.server.wm.app.Components.CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.os.SystemClock;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.compatibility.common.util.TestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:AnimationBackgroundTests
+ */
+@Presubmit
+@android.server.wm.annotation.Group1
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+    @Rule
+    public final DumpOnFailure dumpOnFailure = new DumpOnFailure();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mWmState.setSanityCheckWithFocusedWindow(false);
+        mWmState.waitForDisplayUnfrozen();
+    }
+
+    @After
+    public void tearDown() {
+        mWmState.setSanityCheckWithFocusedWindow(true);
+    }
+
+    @Test
+    public void testBackgroundColorShowsDuringActivityTransition() {
+        assumeTrue(ENABLE_SHELL_TRANSITIONS);
+
+        final List<WindowManagerState.WindowState> windows = getWmState().getWindows();
+        Optional<WindowManagerState.WindowState> screenDecorOverlay =
+                windows.stream().filter(
+                        w -> w.getName().equals("ScreenDecorOverlay")).findFirst();
+        Optional<WindowManagerState.WindowState> screenDecorOverlayBottom =
+                windows.stream().filter(
+                        w -> w.getName().equals("ScreenDecorOverlayBottom")).findFirst();
+        getWmState().getWindowStateForAppToken("screenDecorOverlay");
+        final int screenDecorOverlayHeight = screenDecorOverlay.map(
+                WindowManagerState.WindowState::getRequestedHeight).orElse(0);
+        final int screenDecorOverlayBottomHeight = screenDecorOverlayBottom.map(
+                WindowManagerState.WindowState::getRequestedHeight).orElse(0);
+
+        TestJournalProvider.TestJournalContainer.start();
+        final TestJournalProvider.TestJournal journal = TestJournalProvider.TestJournalContainer
+                .get(CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY);
+        launchActivityInNewTask(CLEAR_BACKGROUND_TRANSITION_EXIT_ACTIVITY);
+
+        try {
+            TestUtils.waitUntil("Waiting for app to complete work", 15 /* timeoutSecond */,
+                    () -> journal.extras.getBoolean(TRANSITION_REQUESTED));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        // The activity transition is set to last 5 seconds, wait half a second to make sure
+        // the activity transition has started after we receive confirmation through the test
+        // journal that we have requested to start a new activity.
+        SystemClock.sleep(500);
+
+        // Take a screenshot during the transition where we hide both the activities to just
+        // show the background of the transition which is set to be white.
+        final Bitmap screenshot = takeScreenshot();
+        final float[] white = new float[] {1, 1, 1};
+        for (int x = 0; x < screenshot.getWidth(); x++) {
+            for (int y = screenDecorOverlayHeight;
+                    y < screenshot.getHeight() - screenDecorOverlayBottomHeight; y++) {
+                final Color c = screenshot.getColor(x, y);
+                assertArrayEquals("Transition Background pixel (" + x + ", " + y + ") is not white",
+                        white, new float[] {c.red(), c.green(), c.blue()}, 0);
+            }
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 07df361..d52f95c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -48,9 +48,9 @@
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.lessThan;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
@@ -58,7 +58,6 @@
 import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -69,7 +68,6 @@
 import android.server.wm.CommandSession.ConfigInfo;
 import android.server.wm.CommandSession.SizeInfo;
 import android.server.wm.TestJournalProvider.TestJournalContainer;
-import android.util.DisplayMetrics;
 import android.view.Display;
 
 import org.junit.Test;
@@ -140,6 +138,8 @@
     @Test
     public void testConfigurationUpdatesWhenRotatingWhileFullscreen() {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
 
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
@@ -163,6 +163,8 @@
     public void testConfigurationUpdatesWhenRotatingWhileDocked() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
@@ -189,6 +191,8 @@
     public void testConfigurationUpdatesWhenRotatingToSideFromDocked() {
         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
 
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
@@ -366,6 +370,9 @@
                 ORIENTATION_PORTRAIT, initialReportedSizes.orientation);
         assertTrue("portrait activity should have height >= width",
                 initialReportedSizes.heightDp >= initialReportedSizes.widthDp);
+        assumeFalse("Skipping test: device is fixed to user rotation",
+                mWmState.isFixedToUserRotation());
+
         separateTestJournal();
 
         launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
@@ -494,11 +501,10 @@
         assertEquals("The last reported size should be the same as the one from onCreate",
                 reportedSizes, onCreateConfigInfo.sizeInfo);
 
-        final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
-        final Point expectedRealDisplaySize = new Point();
-        display.getRealSize(expectedRealDisplaySize);
-
-        final int expectedRotation = display.getRotation();
+        final WindowManagerState.DisplayContent dc = mWmState.getDisplay(Display.DEFAULT_DISPLAY);
+        final Point expectedRealDisplaySize =
+                new Point(dc.getDisplayRect().width(), dc.getDisplayRect().height());
+        final int expectedRotation = mWmState.getRotation();
         assertEquals("The activity should get the final display rotation in onCreate",
                 expectedRotation, onCreateConfigInfo.rotation);
         assertEquals("The application should get the final display rotation in onCreate",
@@ -517,16 +523,16 @@
                 appConfigInfo.sizeInfo.displayWidth > appConfigInfo.sizeInfo.displayHeight);
         assertEquals("The app display metrics must be landscape", isLandscape,
                 appConfigInfo.sizeInfo.metricsWidth > appConfigInfo.sizeInfo.metricsHeight);
-
-        final DisplayMetrics globalMetrics = Resources.getSystem().getDisplayMetrics();
         assertEquals("The display metrics of system resources must be landscape",
-                new Point(globalMetrics.widthPixels, globalMetrics.heightPixels),
+                new Point(dc.getAppRect().width(), dc.getAppRect().height()),
                 new Point(globalSizeInfo.metricsWidth, globalSizeInfo.metricsHeight));
     }
 
     @Test
     public void testTranslucentActivityPermitted() throws Exception {
         assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
 
         final RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
@@ -548,6 +554,8 @@
 
         // Start landscape activity.
         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+        assumeFalse("Skipping test: device is fixed to user rotation",
+                mWmState.isFixedToUserRotation());
         mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
         mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
                 SCREEN_ORIENTATION_LANDSCAPE);
@@ -623,6 +631,8 @@
     public void testAppOrientationWhenRotating() throws Exception {
         assumeTrue("Skipping test: no rotation support", supportsRotation());
 
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         // Start resizeable activity that handles configuration changes.
         separateTestJournal();
         launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
@@ -694,6 +704,8 @@
      */
     private void assertDisplayContextDoesntChangeOrientationWhenRotating(
             Function<Activity, Context> baseContextSupplier) {
+        // TODO(b/209920544) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         RotationSession rotationSession = createManagedRotationSession();
         rotationSession.set(ROTATION_0);
 
@@ -791,8 +803,19 @@
         // Start landscape activity.
         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
-        mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
-                SCREEN_ORIENTATION_LANDSCAPE);
+        final boolean isFixedToUserRotation = mWmState.isFixedToUserRotation();
+        if (!isFixedToUserRotation) {
+            mWmState.waitAndAssertLastOrientation(
+                        "Fullscreen app requested landscape orientation",
+                        SCREEN_ORIENTATION_LANDSCAPE);
+        } else {
+            final SizeInfo reportedSizes =
+                    getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+            assertEquals("landscape activity should be in landscape",
+                    ORIENTATION_LANDSCAPE, reportedSizes.orientation);
+            assertTrue("landscape activity should have height < width",
+                    reportedSizes.heightDp < reportedSizes.widthDp);
+        }
 
         // Start another activity in a different task.
         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
@@ -807,8 +830,18 @@
 
         // Verify that activity brought to front is in originally requested orientation.
         mWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
-        mWmState.waitAndAssertLastOrientation("Should return to app in landscape orientation",
-                SCREEN_ORIENTATION_LANDSCAPE);
+        if (!isFixedToUserRotation) {
+            mWmState.waitAndAssertLastOrientation(
+                        "Fullscreen app requested landscape orientation",
+                        SCREEN_ORIENTATION_LANDSCAPE);
+        } else {
+            final SizeInfo reportedSizes =
+                    getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
+            assertEquals("landscape activity should be in landscape",
+                    ORIENTATION_LANDSCAPE, reportedSizes.orientation);
+            assertTrue("landscape activity should have height < width",
+                    reportedSizes.heightDp < reportedSizes.widthDp);
+        }
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java b/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
index 96cf5c2..efaed22 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AspectRatioTestsBase.java
@@ -30,7 +30,7 @@
 
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.Matcher;
 import org.junit.Before;
@@ -66,7 +66,7 @@
     void runAspectRatioTest(final ActivityTestRule activityRule,
             final AssertAspectRatioCallback callback) {
         final Activity activity = launchActivity(activityRule);
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
         try {
             final Point displaySize = new Point();
             getDisplay(activity).getSize(displaySize);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
index 23e47c2..297eaf0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/BlurTests.java
@@ -363,10 +363,11 @@
     }
 
     private static void assertBackgroundBlurOverBlurBehind(Bitmap screenshot, Rect windowFrame) {
-        // We are assuming that the background blur will become bigger by roughly half of the blur
-        // behind radius
-        assertBlur(screenshot, BACKGROUND_BLUR_PX + ((int) (BLUR_BEHIND_PX*0.5f)),
-                windowFrame.top, windowFrame.bottom);
+        assertBlur(
+                screenshot,
+                (int) Math.sqrt(Math.pow(BACKGROUND_BLUR_PX, 2.f) + Math.pow(BLUR_BEHIND_PX, 2.f)),
+                windowFrame.top,
+                windowFrame.bottom);
     }
 
     private static void assertNoBlurBehind(Bitmap screenshot, Rect windowFrame) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
index 0c56169..d53a168 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
@@ -87,6 +87,8 @@
     private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
             component(SupportsSizeChangesPortraitActivity.class);
 
+    // Fixed orientation min aspect ratio
+    private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f;
     // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
     private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
     // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
@@ -108,7 +110,8 @@
 
         mDisplayMetricsSession =
                 createManagedDisplayMetricsSession(DEFAULT_DISPLAY);
-        createManagedIgnoreOrientationRequestSession(DEFAULT_DISPLAY, /* value=  */ true);
+        createManagedLetterboxAspectRatioSession(DEFAULT_DISPLAY,
+                FIXED_ORIENTATION_MIN_ASPECT_RATIO);
         createManagedConstrainDisplayApisFlagsSession();
     }
 
@@ -487,9 +490,12 @@
      */
     private void runSizeCompatTest(ComponentName activity, int windowingMode,
             boolean inSizeCompatModeAfterResize) {
+        mWmState.computeState();
+        WindowManagerState.DisplayContent originalDC = mWmState.getDisplay(DEFAULT_DISPLAY);
+
         runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 0.5,
                 inSizeCompatModeAfterResize);
-        restoreDisplay(activity);
+        waitForRestoreDisplay(originalDC);
         runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 2,
                 inSizeCompatModeAfterResize);
     }
@@ -552,11 +558,15 @@
     private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed,
             boolean inSizeCompatModeAfterResize) {
         assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
+
+        mWmState.computeState();
+        WindowManagerState.DisplayContent originalDC = mWmState.getDisplay(DEFAULT_DISPLAY);
+
         runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 0.5,
                 inSizeCompatModeAfterResize);
         assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
-        restoreDisplay(activity);
-        runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 2,
+        waitForRestoreDisplay(originalDC);
+        runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio=*/ 2,
                 inSizeCompatModeAfterResize);
         assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
     }
@@ -706,6 +716,18 @@
     }
 
     /**
+     * Wait for the display to be restored to the original display content.
+     */
+    private void waitForRestoreDisplay(WindowManagerState.DisplayContent originalDisplayContent) {
+        mWmState.waitForWithAmState(wmState -> {
+            mDisplayMetricsSession.restoreDisplayMetrics();
+            WindowManagerState.DisplayContent dc = mWmState.getDisplay(DEFAULT_DISPLAY);
+            return dc.equals(originalDisplayContent);
+        }, "waiting for display to be restored");
+    }
+
+
+    /**
      * Resize the display and ensure configuration changes are complete.
      */
     private void resizeDisplay(ComponentName activity, double sizeRatio) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index 28608d0..94731c0 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -21,6 +21,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
 import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_CUTOUT_MODE;
 import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_ORIENTATION;
 import static android.server.wm.DisplayCutoutTests.TestDef.Which.DISPATCHED;
@@ -58,6 +59,8 @@
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
 import android.view.DisplayCutout;
 import android.view.View;
 import android.view.ViewGroup;
@@ -68,11 +71,14 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.FeatureMatcher;
 import org.hamcrest.Matcher;
+import org.junit.AfterClass;
 import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
@@ -94,6 +100,8 @@
 @android.server.wm.annotation.Group3
 @RunWith(Parameterized.class)
 public class DisplayCutoutTests {
+    private static SettingsSession<String> sImmersiveModeConfirmationSetting;
+
     static final String LEFT = "left";
     static final String TOP = "top";
     static final String RIGHT = "right";
@@ -123,6 +131,21 @@
             new ActivityTestRule<>(TestActivity.class, false /* initialTouchMode */,
                     false /* launchActivity */);
 
+    @BeforeClass
+    public static void setUpClass() {
+        sImmersiveModeConfirmationSetting = new SettingsSession<>(
+                Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
+                Settings.Secure::getString, Settings.Secure::putString);
+        sImmersiveModeConfirmationSetting.set("confirmed");
+    }
+
+    @AfterClass
+    public static void tearDownClass() {
+        if (sImmersiveModeConfirmationSetting != null) {
+            sImmersiveModeConfirmationSetting.close();
+        }
+    }
+
     @Test
     public void testConstructor() {
         final Insets safeInsets = Insets.of(1, 2, 3, 4);
@@ -174,6 +197,29 @@
     }
 
     @Test
+    public void testBuilder() {
+        final Insets safeInsets = Insets.of(1, 2, 1, 0);
+        final Insets waterfallInsets = Insets.of(1, 0, 1, 0);
+        final Rect boundingRectTop = new Rect(10, 0, 20, 2);
+        final Path cutoutPath = new Path();
+
+        final DisplayCutout displayCutout = new DisplayCutout.Builder()
+                .setSafeInsets(safeInsets)
+                .setWaterfallInsets(waterfallInsets)
+                .setBoundingRectTop(boundingRectTop)
+                .setCutoutPath(cutoutPath)
+                .build();
+
+        assertEquals(safeInsets.left, displayCutout.getSafeInsetLeft());
+        assertEquals(safeInsets.top, displayCutout.getSafeInsetTop());
+        assertEquals(safeInsets.right, displayCutout.getSafeInsetRight());
+        assertEquals(safeInsets.bottom, displayCutout.getSafeInsetBottom());
+        assertEquals(waterfallInsets, displayCutout.getWaterfallInsets());
+        assertEquals(boundingRectTop, displayCutout.getBoundingRectTop());
+        assertEquals(cutoutPath, displayCutout.getCutoutPath());
+    }
+
+    @Test
     public void testDisplayCutout_default() {
         runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
                 (activity, insets, displayCutout, which) -> {
@@ -526,7 +572,7 @@
         final T activity = rule.launchActivity(
                 new Intent().putExtra(EXTRA_CUTOUT_MODE, cutoutMode)
                         .putExtra(EXTRA_ORIENTATION, orientation));
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
         PollingCheck.waitFor(() -> {
             final Rect appBounds = getAppBounds(activity);
             final Point displaySize = new Point();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
index 366f4dd..1605508 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayHashManagerTest.java
@@ -16,6 +16,9 @@
 
 package android.server.wm;
 
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+import static android.view.Display.DEFAULT_DISPLAY;
 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_INVALID_HASH_ALGORITHM;
@@ -31,14 +34,17 @@
 
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.app.KeyguardManager;
 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.os.PowerManager;
 import android.platform.test.annotations.Presubmit;
 import android.view.Gravity;
+import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
@@ -61,6 +67,7 @@
 import org.junit.Test;
 
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
@@ -68,12 +75,9 @@
 
 @Presubmit
 public class DisplayHashManagerTest {
-    //TODO (b/195136026): There's currently know way to know when the buffer has been drawn in
-    // SurfaceFlinger. Use sleep for now to make sure it's been drawn. Once b/195136026 is
-    // completed, port this code to listen for the transaction complete so we can be sure the buffer
-    // has been latched.
-    private static final int SLEEP_TIME_MS = 1000;
+    private static final int WAIT_TIME_S = 5;
 
+    private final Point mCenter = new Point();
     private final Point mTestViewSize = new Point(200, 300);
 
     private Instrumentation mInstrumentation;
@@ -89,6 +93,8 @@
 
     private SyncDisplayHashResultCallback mSyncDisplayHashResultCallback;
 
+    protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+
     @Rule
     public ActivityTestRule<TestActivity> mActivityRule =
             new ActivityTestRule<>(TestActivity.class);
@@ -96,16 +102,27 @@
     @Before
     public void setUp() throws Exception {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        Context context = mInstrumentation.getContext();
+        final Context context = mInstrumentation.getContext();
+        final KeyguardManager km = context.getSystemService(KeyguardManager.class);
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setClass(context, TestActivity.class);
         mActivity = mActivityRule.getActivity();
 
+        if (km != null && km.isKeyguardLocked() || !Objects.requireNonNull(
+                context.getSystemService(PowerManager.class)).isInteractive()) {
+            pressWakeupButton();
+            pressUnlockButton();
+        }
+
         mActivity.runOnUiThread(() -> {
             mMainView = new RelativeLayout(mActivity);
             mActivity.setContentView(mMainView);
         });
         mInstrumentation.waitForIdleSync();
+        mActivity.runOnUiThread(() -> {
+            mCenter.set((mMainView.getWidth() - mTestViewSize.x) / 2,
+                    (mMainView.getHeight() - mTestViewSize.y) / 2);
+        });
         mDisplayHashManager = context.getSystemService(DisplayHashManager.class);
 
         Set<String> algorithms = mDisplayHashManager.getSupportedHashAlgorithms();
@@ -203,7 +220,10 @@
 
     @Test
     public void testGenerateDisplayHash_ViewOffscreen() {
-        final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+        final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
+
         mInstrumentation.runOnMainSync(() -> {
             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
                     mTestViewSize.y);
@@ -211,17 +231,12 @@
             mTestView.setBackgroundColor(Color.BLUE);
             mTestView.setX(-mTestViewSize.x);
 
-            ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
-            viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
-            viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
             mMainView.addView(mTestView, p);
-            mMainView.invalidate();
+            mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
         });
         mInstrumentation.waitForIdleSync();
         try {
-            viewLayoutLatch.await(5, TimeUnit.SECONDS);
-            Thread.sleep(SLEEP_TIME_MS);
+            committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
         }
 
@@ -237,7 +252,9 @@
         final WindowManager wm = mActivity.getWindowManager();
         final WindowManager.LayoutParams windowParams = new WindowManager.LayoutParams();
 
-        final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+        final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
         mInstrumentation.runOnMainSync(() -> {
             mMainView = new RelativeLayout(mActivity);
             windowParams.width = mTestViewSize.x;
@@ -246,21 +263,27 @@
             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.getViewTreeObserver().addOnWindowAttachListener(
+                    new ViewTreeObserver.OnWindowAttachListener() {
+                        @Override
+                        public void onWindowAttached() {
+                            final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(
+                                    mTestViewSize.x,
+                                    mTestViewSize.y);
+                            mTestView = new View(mActivity);
+                            mTestView.setBackgroundColor(Color.BLUE);
+                            mMainView.addView(mTestView, p);
+                            mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
+                        }
 
-            ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
-            viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
-            viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
-            mMainView.addView(mTestView, p);
+                        @Override
+                        public void onWindowDetached() {
+                        }
+                    });
         });
         mInstrumentation.waitForIdleSync();
         try {
-            viewLayoutLatch.await(5, TimeUnit.SECONDS);
-            Thread.sleep(SLEEP_TIME_MS);
+            committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
         }
 
@@ -357,7 +380,9 @@
 
     @Test
     public void testGenerateAndVerifyDisplayHash_MultiColor() {
-        final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+        final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
         mInstrumentation.runOnMainSync(() -> {
             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
                     mTestViewSize.y);
@@ -376,17 +401,15 @@
             linearLayout.addView(redView, redParams);
             mTestView = linearLayout;
 
-            ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
-            viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
-            viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
-
+            mTestView.setX(mCenter.x);
+            mTestView.setY(mCenter.y);
             mMainView.addView(mTestView, p);
-            mMainView.invalidate();
+            mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
         });
         mInstrumentation.waitForIdleSync();
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
         try {
-            viewLayoutLatch.await(5, TimeUnit.SECONDS);
-            Thread.sleep(SLEEP_TIME_MS);
+            committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
         }
 
@@ -412,22 +435,23 @@
     }
 
     private void setupChildView() {
-        final CountDownLatch viewLayoutLatch = new CountDownLatch(2);
+        final CountDownLatch committedCallbackLatch = new CountDownLatch(1);
+        final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        t.addTransactionCommittedListener(mExecutor, committedCallbackLatch::countDown);
+
         mInstrumentation.runOnMainSync(() -> {
             final RelativeLayout.LayoutParams p = new RelativeLayout.LayoutParams(mTestViewSize.x,
                     mTestViewSize.y);
             mTestView = new View(mActivity);
+            mTestView.setX(mCenter.x);
+            mTestView.setY(mCenter.y);
             mTestView.setBackgroundColor(Color.BLUE);
-            ViewTreeObserver viewTreeObserver = mTestView.getViewTreeObserver();
-            viewTreeObserver.addOnGlobalLayoutListener(viewLayoutLatch::countDown);
-            viewTreeObserver.registerFrameCommitCallback(viewLayoutLatch::countDown);
             mMainView.addView(mTestView, p);
-            mMainView.invalidate();
+            mMainView.getRootSurfaceControl().applyTransactionOnDraw(t);
         });
         mInstrumentation.waitForIdleSync();
         try {
-            viewLayoutLatch.await(5, TimeUnit.SECONDS);
-            Thread.sleep(SLEEP_TIME_MS);
+            committedCallbackLatch.await(WAIT_TIME_S, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
         }
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
index 4a49d9a..aa30d0c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/HideOverlayWindowsTest.java
@@ -65,7 +65,7 @@
     public void setUp() throws Exception {
         super.setUp();
         mPongReceiver = new PongReceiver();
-        mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG));
+        mContext.registerReceiver(mPongReceiver, new IntentFilter(PONG), Context.RECEIVER_EXPORTED);
     }
 
     @After
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
index 6ee0eb4..2dbc649 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardInputTests.java
@@ -30,7 +30,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -54,7 +54,7 @@
         assumeTrue(supportsInsecureLock());
 
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index dc7c308..30c0555 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -46,6 +46,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
@@ -187,6 +188,8 @@
      */
     @Test
     public void testTranslucentShowWhenLockedActivity() {
+        // TODO(b/209906849) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
         mWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
@@ -219,6 +222,8 @@
 
     @Test
     public void testDialogShowWhenLockedActivity() {
+        // TODO(b/209906849) remove assumeFalse after issue fix.
+        assumeFalse(ENABLE_SHELL_TRANSITIONS);
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
         mWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
index a4b0e52..1b21043 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationInWindowTests.java
@@ -45,6 +45,7 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.Matcher;
 import org.junit.Before;
@@ -162,7 +163,7 @@
             LayoutParams lp) {
         final T activity = rule.launchActivity(
                 new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
         return activity;
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
index 9c9491c..b7b8376 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/LocationOnScreenTests.java
@@ -54,6 +54,7 @@
 
 import com.android.compatibility.common.util.BitmapUtils;
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.Matcher;
 import org.junit.Assert;
@@ -148,7 +149,7 @@
             LayoutParams lp) {
         final T activity = rule.launchActivity(
                 new Intent().putExtra(EXTRA_LAYOUT_PARAMS, lp));
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
         return activity;
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
index 3e5b5d6..b372d53 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ManifestLayoutTests.java
@@ -36,13 +36,13 @@
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.WindowManagerState.WindowState;
+import android.util.DisplayMetrics;
 import android.view.DisplayCutout;
 import android.view.WindowMetrics;
 
 import org.junit.Test;
 
 import java.util.List;
-import android.util.DisplayMetrics;
 
 /**
  * Build/Install/Run:
@@ -121,12 +121,11 @@
         // Use default density because ActivityInfo.WindowLayout is initialized by that.
         final int minWidth = dpToPx(MIN_WIDTH_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
         final int minHeight = dpToPx(MIN_HEIGHT_DP, DisplayMetrics.DENSITY_DEVICE_STABLE);
-        final Rect containingRect = mWindowState.getContainingFrame();
+        final Rect parentFrame = mWindowState.getParentFrame();
         final int cutoutSize = getCutoutSizeByHorGravity(GRAVITY_HOR_LEFT);
 
-        assertEquals("Min width is incorrect", minWidth,
-                containingRect.width() + cutoutSize);
-        assertEquals("Min height is incorrect", minHeight, containingRect.height());
+        assertEquals("Min width is incorrect", minWidth, parentFrame.width() + cutoutSize);
+        assertEquals("Min height is incorrect", minHeight, parentFrame.height());
     }
 
     private void testLayout(
@@ -147,7 +146,7 @@
 
         getDisplayAndWindowState(activityName, true);
 
-        final Rect containingRect = mWindowState.getContainingFrame();
+        final Rect parentFrame = mWindowState.getParentFrame();
         final WindowMetrics windowMetrics = mWm.getMaximumWindowMetrics();
         final Rect stableBounds = new Rect(windowMetrics.getBounds());
         stableBounds.inset(windowMetrics.getWindowInsets().getInsetsIgnoringVisibility(
@@ -166,7 +165,7 @@
         }
 
         verifyFrameSizeAndPosition(vGravity, hGravity, expectedWidthPx, expectedHeightPx,
-                containingRect, stableBounds);
+                parentFrame, stableBounds);
     }
 
     private void getDisplayAndWindowState(ComponentName activityName, boolean checkFocus)
@@ -196,24 +195,24 @@
 
     private void verifyFrameSizeAndPosition(
             int vGravity, int hGravity, int expectedWidthPx, int expectedHeightPx,
-            Rect containingFrame, Rect parentFrame) {
+            Rect parentFrame, Rect stableBounds) {
         final int cutoutSize = getCutoutSizeByHorGravity(hGravity);
         assertEquals("Width is incorrect",
-                expectedWidthPx, containingFrame.width() + cutoutSize);
-        assertEquals("Height is incorrect", expectedHeightPx, containingFrame.height());
+                expectedWidthPx, parentFrame.width() + cutoutSize);
+        assertEquals("Height is incorrect", expectedHeightPx, parentFrame.height());
 
         if (vGravity == GRAVITY_VER_TOP) {
-            assertEquals("Should be on the top", parentFrame.top, containingFrame.top);
+            assertEquals("Should be on the top", stableBounds.top, parentFrame.top);
         } else if (vGravity == GRAVITY_VER_BOTTOM) {
-            assertEquals("Should be on the bottom", parentFrame.bottom, containingFrame.bottom);
+            assertEquals("Should be on the bottom", stableBounds.bottom, parentFrame.bottom);
         }
 
         if (hGravity == GRAVITY_HOR_LEFT) {
             assertEquals("Should be on the left",
-                    parentFrame.left, containingFrame.left - cutoutSize);
+                    stableBounds.left, parentFrame.left - cutoutSize);
         } else if (hGravity == GRAVITY_HOR_RIGHT){
             assertEquals("Should be on the right",
-                    parentFrame.right, containingFrame.right + cutoutSize);
+                    stableBounds.right, parentFrame.right + cutoutSize);
         }
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
index d468092..a061f8c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MinimalPostProcessingTests.java
@@ -26,10 +26,8 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertTrue;
 
 import android.content.ComponentName;
-import android.content.pm.PackageManager;
 import android.platform.test.annotations.Presubmit;
 
 import org.junit.Test;
@@ -68,21 +66,11 @@
 
     private void assertDisplayRequestedMinimalPostProcessing(ComponentName name, boolean on) {
         final int displayId = getDisplayId(name);
+
+        // TODO(b/202378408) verify that minimal post-processing is requested only if
+        // it's supported once we have a separate API for disabling on-device processing.
         boolean requested = isMinimalPostProcessingRequested(displayId);
-
-        PackageManager packageManager = mContext.getPackageManager();
-        // For TV Android S is requesting minimal post processing regardless if it's supported,
-        // because the same signal is used by HAL implementations to disable on-device processing.
-        final boolean isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK);
-        if (isTv) {
-            // TODO(b/202378408): Verify that minimal post-processing is requested only if
-            // it's supported once we have a separate API for disabling on-device processing.
-            assertEquals(requested, on);
-            return;
-        }
-
-        boolean supported = isMinimalPostProcessingSupported(displayId);
-        assertTrue(supported ? requested == on : !requested);
+        assertEquals(requested, on);
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index 58df952..e7d6650 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -570,7 +570,9 @@
         waitAndAssertActivityStateOnDisplay(TEST_ACTIVITY, STATE_RESUMED, newDisplay.mId,
                 "Activity launched on secondary display must be resumed");
 
-        tapOnDisplayCenter(DEFAULT_DISPLAY);
+        // Tap on task center to switch focus between displays. Using task center instead of
+        // display center to cover the multi window scenario.
+        tapOnTaskCenter(mWmState.getTaskByActivity(VIRTUAL_DISPLAY_ACTIVITY));
 
         waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
                 "Top activity must be on the primary display");
@@ -624,7 +626,9 @@
         assertBothDisplaysHaveResumedActivities(pair(DEFAULT_DISPLAY, RESIZEABLE_ACTIVITY),
                 pair(newDisplay.mId, TEST_ACTIVITY));
 
-        tapOnDisplayCenter(DEFAULT_DISPLAY);
+        // Tap on task center to switch focus between displays. Using task center instead of
+        // display center to cover the multi window scenario.
+        tapOnTaskCenter(mWmState.getTaskByActivity(RESIZEABLE_ACTIVITY));
 
         // Check that the activity on the primary display is the topmost resumed
         waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
@@ -675,7 +679,9 @@
         mWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
                 VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
 
-        tapOnDisplayCenter(DEFAULT_DISPLAY);
+        // Tap on task center to switch focus between displays. Using task center instead of
+        // display center to cover the multi window scenario.
+        tapOnTaskCenter(mWmState.getTaskByActivity(TEST_ACTIVITY));
 
         waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity should be top resumed when tapped.");
@@ -869,7 +875,9 @@
         waitAndAssertTopResumedActivity(SDK_27_TEST_ACTIVITY, newDisplay.mId,
                 "Activity launched on secondary display must be resumed and focused");
 
-        tapOnDisplayCenter(DEFAULT_DISPLAY);
+        // Tap on task center to switch focus between displays. Using task center instead of
+        // display center to cover the multi window scenario.
+        tapOnTaskCenter(mWmState.getTaskByActivity(SDK_27_LAUNCHING_ACTIVITY));
         waitAndAssertTopResumedActivity(SDK_27_LAUNCHING_ACTIVITY, DEFAULT_DISPLAY,
                 "Activity launched on default display must be resumed and focused");
         assertEquals("There must be only one resumed activity in the package.", 1,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index cb2fb3f..2a22f37 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -17,6 +17,8 @@
 package android.server.wm;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.server.wm.BarTestUtils.assumeHasBars;
+import static android.server.wm.MockImeHelper.createManagedMockImeSession;
 import static android.server.wm.UiDeviceUtils.pressSleepButton;
 import static android.server.wm.UiDeviceUtils.pressUnlockButton;
 import static android.server.wm.UiDeviceUtils.pressWakeupButton;
@@ -28,12 +30,11 @@
 import static android.server.wm.app.Components.TEST_LIVE_WALLPAPER_SERVICE;
 import static android.server.wm.app.Components.TestLiveWallpaperKeys.COMPONENT;
 import static android.server.wm.app.Components.TestLiveWallpaperKeys.ENGINE_DISPLAY_ID;
-import static android.server.wm.BarTestUtils.assumeHasBars;
-import static android.server.wm.MockImeHelper.createManagedMockImeSession;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
@@ -64,11 +65,12 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
-import android.server.wm.WindowManagerState.DisplayContent;
 import android.server.wm.TestJournalProvider.TestJournalContainer;
+import android.server.wm.WindowManagerState.DisplayContent;
 import android.server.wm.WindowManagerState.WindowState;
 import android.server.wm.intent.Activities;
 import android.text.TextUtils;
+import android.view.Window;
 import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -76,7 +78,6 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
-import com.android.compatibility.common.util.ImeAwareEditText;
 import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.TestUtils;
 import com.android.cts.mockime.ImeCommand;
@@ -497,9 +498,11 @@
         final DisplayContent defDisplay = mWmState.getDisplay(DEFAULT_DISPLAY);
         final ImeEventStream stream = mockImeSession.openEventStream();
 
-        // Tap default display as top focused display & request focus on EditText to show
-        // soft input.
-        tapOnDisplayCenter(defDisplay.mId);
+        // Tap on the imeTestActivity task center instead of the display center because
+        // the activity might not be spanning the entire display
+        WindowManagerState.Task imeTestActivityTask = mWmState
+                .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+        tapOnTaskCenter(imeTestActivityTask);
         expectEvent(stream, editorMatcher("onStartInput",
                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
@@ -511,8 +514,11 @@
                 imeTestActivitySession2.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(newDisplay.mId, imeTestActivitySession2, stream);
 
-        // Tap default display again to make sure the IME window will come back.
-        tapOnDisplayCenter(defDisplay.mId);
+        // Tap on the imeTestActivity task center instead of the display center because
+        // the activity might not be spanning the entire display
+        imeTestActivityTask = mWmState
+                .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+        tapOnTaskCenter(imeTestActivityTask);
         expectEvent(stream, editorMatcher("onStartInput",
                 imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
         showSoftInputAndAssertImeShownOnDisplay(defDisplay.mId, imeTestActivitySession, stream);
@@ -698,13 +704,19 @@
                 .setDisplayId(DEFAULT_DISPLAY).execute();
         waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
                 DEFAULT_DISPLAY, "Activity launched on default display and on top");
-        final ImeEventStream stream = mockImeSession.openEventStream();
-        expectEvent(stream, editorMatcher("onStartInput",
-                imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
 
         // Activity is no longer on the secondary display
         assertThat(mWmState.hasActivityInDisplay(newDisplay.mId, imeTestActivityName)).isFalse();
 
+        // Tap on the imeTestActivity task center instead of the display center because
+        // the activity might not be spanning the entire display
+        final ImeEventStream stream = mockImeSession.openEventStream();
+        final WindowManagerState.Task testActivityTask = mWmState
+                .getTaskByActivity(imeTestActivitySession.getActivity().getComponentName());
+        tapOnTaskCenter(testActivityTask);
+        expectEvent(stream, editorMatcher("onStartInput",
+                imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()), TIMEOUT);
+
         // Verify the activity shows soft input on the default display.
         showSoftInputAndAssertImeShownOnDisplay(DEFAULT_DISPLAY, imeTestActivitySession, stream);
     }
@@ -721,10 +733,15 @@
                 .setShowSystemDecorations(true)
                 .setDisplayImePolicy(DISPLAY_IME_POLICY_LOCAL)
                 .setSimulateDisplay(true)
+                .setResizeDisplay(false)
                 .createDisplays(2);
         final DisplayContent firstDisplay = newDisplays.get(0);
         final DisplayContent secondDisplay = newDisplays.get(1);
 
+        // Skip if the test environment somehow didn't create 2 displays with identical size.
+        assumeTrue("Skip the test if the size of the created displays aren't identical",
+                firstDisplay.getDisplayRect().equals(secondDisplay.getDisplayRect()));
+
         // Initialize IME test environment
         final MockImeSession mockImeSession = createManagedMockImeSession(this);
         final TestActivitySession<ImeTestActivity> imeTestActivitySession =
@@ -745,63 +762,73 @@
                 editorMatcher("onStartInput",
                         imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
                 event -> "showSoftInput".equals(event.getEventName()));
-        // Launch Ime must not lead to screen size changes.
-        waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
+        try {
+            // Launch Ime must not lead to screen size changes.
+            waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
 
-        final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
-                mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
-                .getReturnParcelableValue();
+            final Rect currentBoundsOnFirstDisplay = expectCommand(stream,
+                    mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+                    .getReturnParcelableValue();
 
-        // Clear onConfigurationChanged events before IME moves to the secondary display to prevent
-        // flaky because IME may receive configuration updates which we don't care about.
-        // An example is CONFIG_KEYBOARD_HIDDEN.
-        configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
+            // Clear onConfigurationChanged events before IME moves to the secondary display to
+            // prevent flaky because IME may receive configuration updates which we don't care
+            // about. An example is CONFIG_KEYBOARD_HIDDEN.
+            configChangeVerifyStream = clearOnConfigurationChangedFromStream(stream);
 
-        // Tap secondDisplay to change it to the top focused display.
-        tapOnDisplayCenter(secondDisplay.mId);
+            // Tap secondDisplay to change it to the top focused display.
+            tapOnDisplayCenter(secondDisplay.mId);
 
-        // Move ImeTestActivity from firstDisplay to secondDisplay.
-        getLaunchActivityBuilder()
-                .setUseInstrumentation()
-                .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
-                .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-                .allowMultipleInstances(false)
-                .setDisplayId(secondDisplay.mId).execute();
+            // Move ImeTestActivity from firstDisplay to secondDisplay.
+            getLaunchActivityBuilder()
+                    .setUseInstrumentation()
+                    .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
+                    .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .allowMultipleInstances(false)
+                    .setDisplayId(secondDisplay.mId).execute();
 
-        // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
-        waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
-                secondDisplay.mId, "ImeTestActivity must be top-resumed on display#"
-                + secondDisplay.mId);
-        assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
-                imeTestActivitySession.getActivity().getComponentName())).isFalse();
+            // Make sure ImeTestActivity is move from the firstDisplay to the secondDisplay
+            waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
+                    secondDisplay.mId, "ImeTestActivity must be top-resumed on display#"
+                            + secondDisplay.mId);
+            assertThat(mWmState.hasActivityInDisplay(firstDisplay.mId,
+                    imeTestActivitySession.getActivity().getComponentName())).isFalse();
 
-        // Show soft input again to trigger IME movement.
-        imeTestActivitySession.runOnMainSyncAndWait(
-                imeTestActivitySession.getActivity()::showSoftInput);
+            // Show soft input again to trigger IME movement.
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
 
-        waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
-                editorMatcher("onStartInput",
-                        imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
-                event -> "showSoftInput".equals(event.getEventName()));
-        // Moving IME to the display with the same display metrics must not lead to
-        // screen size changes.
-        waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
+            waitOrderedImeEventsThenAssertImeShown(stream, secondDisplay.mId,
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
 
-        final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
-                mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
-                .getReturnParcelableValue();
+            // Moving IME to the display with the same display metrics must not lead to
+            // screen size changes.
+            waitAndAssertImeNoScreenSizeChanged(configChangeVerifyStream);
 
-        assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
-                .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
+            final Rect currentBoundsOnSecondDisplay = expectCommand(stream,
+                    mockImeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+                    .getReturnParcelableValue();
+
+            assertWithMessage("The current WindowMetrics bounds of IME must not be changed.")
+                    .that(currentBoundsOnFirstDisplay).isEqualTo(currentBoundsOnSecondDisplay);
+        } catch (AssertionError e) {
+            mWmState.computeState();
+            final Rect displayRect1 = mWmState.getDisplay(firstDisplay.mId).getDisplayRect();
+            final Rect displayRect2 = mWmState.getDisplay(secondDisplay.mId).getDisplayRect();
+            assumeTrue("Skip test since the size of one or both displays happens unexpected change",
+                    displayRect1.equals(displayRect2));
+            throw e;
+        }
     }
 
     public static class ImeTestActivity extends Activity {
-        ImeAwareEditText mEditText;
+        EditText mEditText;
 
         @Override
         protected void onCreate(Bundle icicle) {
             super.onCreate(icicle);
-            mEditText = new ImeAwareEditText(this);
+            mEditText = new EditText(this);
             // Set private IME option for editorMatcher to identify which TextView received
             // onStartInput event.
             resetPrivateImeOptionsIdentifier();
@@ -809,6 +836,9 @@
             layout.setOrientation(LinearLayout.VERTICAL);
             layout.addView(mEditText);
             mEditText.requestFocus();
+            // SOFT_INPUT_STATE_UNSPECIFIED may produced unexpected behavior for CTS. To make tests
+            // deterministic, using SOFT_INPUT_STATE_UNCHANGED instead.
+            setUnchangedSoftInputState();
             setContentView(layout);
         }
 
@@ -821,6 +851,15 @@
             mEditText.setPrivateImeOptions(
                     getClass().getName() + "/" + Long.toString(SystemClock.elapsedRealtimeNanos()));
         }
+
+        private void setUnchangedSoftInputState() {
+            final Window window = getWindow();
+            final int currentSoftInputMode = window.getAttributes().softInputMode;
+            final int newSoftInputMode =
+                    (currentSoftInputMode & ~WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE)
+                            | SOFT_INPUT_STATE_UNCHANGED;
+            window.setSoftInputMode(newSoftInputMode);
+        }
     }
 
     public static class ImeTestActivity2 extends ImeTestActivity { }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 98ad738..462e29f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -350,7 +350,7 @@
         return mObjectTracker.manage(new DisplayMetricsSession(displayId));
     }
 
-    public static class IgnoreOrientationRequestSession implements AutoCloseable {
+    public static class LetterboxAspectRatioSession implements AutoCloseable {
         private static final String WM_SET_IGNORE_ORIENTATION_REQUEST =
                 "wm set-ignore-orientation-request ";
         private static final String WM_GET_IGNORE_ORIENTATION_REQUEST =
@@ -358,31 +358,38 @@
         private static final Pattern IGNORE_ORIENTATION_REQUEST_PATTERN =
                 Pattern.compile("ignoreOrientationRequest (true|false) for displayId=\\d+");
 
-        final int mDisplayId;
-        final boolean mInitialValue;
+        private static final String WM_SET_LETTERBOX_STYLE_ASPECT_RATIO =
+                "wm set-letterbox-style --aspectRatio ";
+        private static final String WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO
+                = "wm reset-letterbox-style aspectRatio";
 
-        IgnoreOrientationRequestSession(int displayId, boolean value) {
+        final int mDisplayId;
+        final boolean mInitialIgnoreOrientationRequest;
+
+        LetterboxAspectRatioSession(int displayId, float aspectRatio) {
             mDisplayId = displayId;
             Matcher matcher = IGNORE_ORIENTATION_REQUEST_PATTERN.matcher(
                     executeShellCommand(WM_GET_IGNORE_ORIENTATION_REQUEST + " -d " + mDisplayId));
             assertTrue("get-ignore-orientation-request should match pattern", matcher.find());
-            mInitialValue = Boolean.parseBoolean(matcher.group(1));
+            mInitialIgnoreOrientationRequest = Boolean.parseBoolean(matcher.group(1));
 
             executeShellCommand("wm set-ignore-orientation-request true -d " + mDisplayId);
-            executeShellCommand(WM_SET_IGNORE_ORIENTATION_REQUEST + value + " -d " + mDisplayId);
+            executeShellCommand(WM_SET_LETTERBOX_STYLE_ASPECT_RATIO + aspectRatio);
         }
 
         @Override
         public void close() {
             executeShellCommand(
-                    WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialValue + " -d " + mDisplayId);
+                    WM_SET_IGNORE_ORIENTATION_REQUEST + mInitialIgnoreOrientationRequest + " -d "
+                            + mDisplayId);
+            executeShellCommand(WM_RESET_LETTERBOX_STYLE_ASPECT_RATIO);
         }
     }
 
     /** @see ObjectTracker#manage(AutoCloseable) */
-    protected IgnoreOrientationRequestSession createManagedIgnoreOrientationRequestSession(
-            int displayId, boolean value) {
-        return mObjectTracker.manage(new IgnoreOrientationRequestSession(displayId, value));
+    protected LetterboxAspectRatioSession createManagedLetterboxAspectRatioSession(int displayId,
+            float aspectRatio) {
+        return mObjectTracker.manage(new LetterboxAspectRatioSession(displayId, aspectRatio));
     }
 
     void waitForDisplayGone(Predicate<DisplayContent> displayPredicate) {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 74a6e3a9..13877bd 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -23,6 +23,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.server.wm.CliIntentExtra.extraBool;
 import static android.server.wm.CliIntentExtra.extraString;
 import static android.server.wm.ComponentNameUtils.getActivityName;
@@ -105,8 +107,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteCallback;
-import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
 import android.server.wm.CommandSession.ActivityCallback;
 import android.server.wm.CommandSession.SizeInfo;
@@ -148,11 +150,6 @@
     private static final int ROTATION_180 = 2;
     private static final int ROTATION_270 = 3;
 
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
-    private static final int ORIENTATION_LANDSCAPE = 0;
-    // Corresponds to ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
-    private static final int ORIENTATION_PORTRAIT = 1;
-
     private static final float FLOAT_COMPARE_EPSILON = 0.005f;
 
     // Corresponds to com.android.internal.R.dimen.config_pictureInPictureMinAspectRatio
@@ -249,10 +246,10 @@
     public void testEnterPipToOtherOrientation() {
         // Launch a portrait only app on the fullscreen stack
         launchActivity(TEST_ACTIVITY,
-                extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)));
+                extraString(EXTRA_FIXED_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)));
         // Launch the PiP activity fixed as landscape
         launchActivity(PIP_ACTIVITY,
-                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_LANDSCAPE)));
+                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_LANDSCAPE)));
         // Enter PiP, and assert that the PiP is within bounds now that the device is back in
         // portrait
         mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
@@ -453,7 +450,7 @@
     public void testAutoEnterPictureInPictureOnUserLeaveHintWhenPipRequestedNotOverridden()
             {
         // Launch a test activity so that we're not over home
-        launchActivity(TEST_ACTIVITY);
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
 
         // Launch the PIP activity that enters PIP on user leave hint, not on PIP requested
         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT, "true"));
@@ -1083,22 +1080,21 @@
         assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
         // Launch the PiP activity fixed as portrait, and enter picture-in-picture
         launchActivity(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
-                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(ORIENTATION_PORTRAIT)),
+                extraString(EXTRA_PIP_ORIENTATION, String.valueOf(SCREEN_ORIENTATION_PORTRAIT)),
                 extraString(EXTRA_ENTER_PIP, "true"));
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
 
         // Request that the orientation is set to landscape
-        mBroadcastActionTrigger.requestOrientationForPip(ORIENTATION_LANDSCAPE);
+        mBroadcastActionTrigger.requestOrientationForPip(SCREEN_ORIENTATION_LANDSCAPE);
 
         // Launch the activity back into fullscreen and ensure that it is now in landscape
         launchActivity(PIP_ACTIVITY);
         waitForExitPipToFullscreen(PIP_ACTIVITY);
         assertPinnedStackDoesNotExist();
-        mWmState.waitForActivityOrientation(PIP_ACTIVITY, ORIENTATION_LANDSCAPE);
-
-        final Task pipActivityTask = mWmState.getTaskByActivity(PIP_ACTIVITY);
-        assertEquals(ORIENTATION_LANDSCAPE, pipActivityTask.mOverrideConfiguration.orientation);
+        assertTrue("The PiP activity in fullscreen must be landscape",
+                mWmState.waitForActivityOrientation(
+                        PIP_ACTIVITY, Configuration.ORIENTATION_LANDSCAPE));
     }
 
     @Test
@@ -1252,7 +1248,7 @@
     @Test
     public void testDisplayMetricsPinUnpin() {
         separateTestJournal();
-        launchActivity(TEST_ACTIVITY);
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         final int defaultWindowingMode = mWmState
                 .getTaskByActivity(TEST_ACTIVITY).getWindowingMode();
         final SizeInfo initialSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
@@ -1286,14 +1282,14 @@
     @Test
     public void testAutoPipAllowedBypassesExplicitEnterPip() {
         // Launch a test activity so that we're not over home.
-        launchActivity(TEST_ACTIVITY);
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
 
         // Launch the PIP activity and set its pip params to allow auto-pip.
         launchActivity(PIP_ACTIVITY, extraString(EXTRA_ALLOW_AUTO_PIP, "true"));
         assertPinnedStackDoesNotExist();
 
         // Launch a new activity and ensure that there is a pinned stack.
-        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY);
+        launchActivity(RESUME_WHILE_PAUSING_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
@@ -1306,7 +1302,7 @@
         assertPinnedStackDoesNotExist();
 
         // Launch another and ensure that there is a pinned stack.
-        launchActivity(TEST_ACTIVITY);
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
         waitForEnterPip(PIP_ACTIVITY);
         assertPinnedStackExists();
         waitAndAssertActivityState(PIP_ACTIVITY, STATE_PAUSED, "activity must be paused");
@@ -1541,6 +1537,9 @@
             return activity.getWindowingMode() == WINDOWING_MODE_PINNED
                     && activity.getState().equals(STATE_PAUSED);
         }, "checking activity windowing mode");
+        if (ENABLE_SHELL_TRANSITIONS) {
+            mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        }
     }
 
     /**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
index 49d2dba..c39aaa2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PrivacyIndicatorBoundsTests.java
@@ -16,12 +16,13 @@
 
 package android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE;
 import static android.server.wm.RoundedCornerTests.TestActivity.EXTRA_ORIENTATION;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
@@ -29,14 +30,11 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 
 import android.app.Activity;
-import android.app.AppOpsManager;
-import android.content.Context;
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Bundle;
@@ -101,13 +99,24 @@
         final View childWindowRoot = activity.getChildWindowRoot();
         PollingCheck.waitFor(TIMEOUT_MS, () -> childWindowRoot.getWidth() > 0);
         PollingCheck.waitFor(TIMEOUT_MS, () -> activity.getDispatchedInsets() != null);
+        mWmState.waitForValidState(mTestActivity.getActivity().getComponentName());
         WindowInsets insets = activity.getDispatchedInsets();
         assertNotNull(insets);
         Rect screenBounds = activity.getScreenBounds();
         assertNotNull(screenBounds);
         Rect bounds = insets.getPrivacyIndicatorBounds();
         assertNotNull(bounds);
-        assertEquals(bounds.top, 0);
+        final int windowingMode = mWmState
+                .getTaskDisplayArea(mTestActivity.getActivity().getComponentName())
+                .getWindowingMode();
+        final boolean inMultiWindowMode = windowingMode != WINDOWING_MODE_FULLSCREEN
+                && windowingMode != WINDOWING_MODE_UNDEFINED;
+        if (!inMultiWindowMode) {
+            // Multi-window environments may place the indicator bounds somewhere other than the
+            // top (e.g. desktops may decide that the bottom-right corner has the highest visual
+            // priority). Other windowing modes
+            assertEquals(bounds.top, 0);
+        }
         // TODO 188788786: Figure out why the screen bounds are different in cuttlefish,
         // causing failures
         // assertTrue(bounds + " not contained in " + screenBounds, screenBounds.contains(bounds));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
index b49e726..4c03c1a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplashscreenTests.java
@@ -33,8 +33,11 @@
 import static android.server.wm.app.Components.SPLASHSCREEN_ACTIVITY;
 import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_ICON_ACTIVITY;
 import static android.server.wm.app.Components.SPLASH_SCREEN_REPLACE_THEME_ACTIVITY;
+import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITIES;
 import static android.server.wm.app.Components.TestActivity.COMMAND_START_ACTIVITY;
 import static android.server.wm.app.Components.TestActivity.EXTRA_INTENT;
+import static android.server.wm.app.Components.TestActivity.EXTRA_INTENTS;
+import static android.server.wm.app.Components.TestActivity.EXTRA_OPTION;
 import static android.server.wm.app.Components.TestStartingWindowKeys.CANCEL_HANDLE_EXIT;
 import static android.server.wm.app.Components.TestStartingWindowKeys.CENTER_VIEW_IS_SURFACE_VIEW;
 import static android.server.wm.app.Components.TestStartingWindowKeys.CONTAINS_BRANDING_VIEW;
@@ -67,6 +70,7 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.ActivityOptions;
 import android.app.UiModeManager;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -82,6 +86,7 @@
 import android.platform.test.annotations.Presubmit;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
+import android.window.SplashScreen;
 
 import androidx.core.graphics.ColorUtils;
 
@@ -131,6 +136,22 @@
 
     private void startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity,
             ComponentName componentName, Consumer<Intent> fillExtra) {
+        startActivityFromTestLauncher(homeActivity, componentName, fillExtra, null /* options */);
+    }
+
+    private void startActivitiesFromTestLauncher(CommandSession.ActivitySession homeActivity,
+            Intent[] intents, ActivityOptions options) {
+
+        final Bundle data = new Bundle();
+        data.putParcelableArray(EXTRA_INTENTS, intents);
+        if (options != null) {
+            data.putParcelable(EXTRA_OPTION, options.toBundle());
+        }
+        homeActivity.sendCommand(COMMAND_START_ACTIVITIES, data);
+    }
+
+    private void startActivityFromTestLauncher(CommandSession.ActivitySession homeActivity,
+            ComponentName componentName, Consumer<Intent> fillExtra, ActivityOptions options) {
 
         final Bundle data = new Bundle();
         final Intent startIntent = new Intent();
@@ -138,6 +159,9 @@
         startIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         fillExtra.accept(startIntent);
         data.putParcelable(EXTRA_INTENT, startIntent);
+        if (options != null) {
+            data.putParcelable(EXTRA_OPTION, options.toBundle());
+        }
         homeActivity.sendCommand(COMMAND_START_ACTIVITY, data);
     }
 
@@ -358,18 +382,9 @@
 
         mWmState.computeState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY);
         mWmState.assertVisibility(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, true);
-        final TestJournalProvider.TestJournal journal =
-                TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT);
         if (expectResult) {
-            TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
-                    () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
-            assertTrue("No entry for CONTAINS_CENTER_VIEW",
-                    journal.extras.containsKey(CONTAINS_CENTER_VIEW));
-            assertTrue("No entry for CONTAINS_BRANDING_VIEW",
-                    journal.extras.containsKey(CONTAINS_BRANDING_VIEW));
-            assertTrue("Center View shouldn't be null", journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
-            assertTrue(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
-            assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW));
+            assertHandleExit(TestJournalProvider.TestJournalContainer
+                    .get(HANDLE_SPLASH_SCREEN_EXIT));
         }
     }
 
@@ -433,17 +448,8 @@
         });
         mWmState.computeState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
         mWmState.assertVisibility(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, true);
-        final TestJournalProvider.TestJournal journal =
-                TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
-        TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
-                () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
-        assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
-        final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
-        final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION);
-        assertTrue(iconAnimationStart != 0);
-        assertEquals(iconAnimationDuration, 500);
-        assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
-        assertTrue(journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW));
+
+        assertReplaceIcon(TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT));
     }
 
     @Test
@@ -512,6 +518,111 @@
     }
 
     @Test
+    public void testLaunchFromLauncherWithEmptyIconOptions() {
+        assumeFalse(isLeanBack());
+        final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
+        TestJournalProvider.TestJournalContainer.start();
+        final ActivityOptions noIconOptions = ActivityOptions.makeBasic()
+                .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
+        startActivityFromTestLauncher(homeActivity, SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, intent ->
+                intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true), noIconOptions);
+        mWmState.waitForActivityState(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY, STATE_RESUMED);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+        assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+    }
+
+    @Test
+    public void testLaunchAppWithIconOptions() throws Exception {
+        final Bundle bundle = ActivityOptions.makeBasic()
+                .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON).toBundle();
+        TestJournalProvider.TestJournalContainer.start();
+        final Intent intent = new Intent(Intent.ACTION_VIEW)
+                .setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+                .setFlags(FLAG_ACTIVITY_NEW_TASK);
+        intent.putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true);
+        mContext.startActivity(intent, bundle);
+
+        mWmState.waitForActivityState(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY, STATE_RESUMED);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+        assertHandleExit(TestJournalProvider.TestJournalContainer.get(HANDLE_SPLASH_SCREEN_EXIT));
+    }
+
+    private void launchActivitiesFromLauncherWithOptions(Intent[] intents,
+            ActivityOptions options, ComponentName waitResumeComponent) {
+        assumeFalse(isLeanBack());
+        final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
+        TestJournalProvider.TestJournalContainer.start();
+
+        startActivitiesFromTestLauncher(homeActivity, intents, options);
+
+        mWmState.waitForActivityState(waitResumeComponent, STATE_RESUMED);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testLaunchActivitiesWithIconOptions() throws Exception {
+        final ActivityOptions options = ActivityOptions.makeBasic()
+                .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
+        final Intent[] intents = new Intent[] {
+                new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
+                new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY)
+                        .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true)
+        };
+        launchActivitiesFromLauncherWithOptions(intents, options,
+                SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
+        assertReplaceIcon(TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT));
+    }
+
+    @Test
+    public void testLaunchActivitiesWithEmptyOptions() {
+        final ActivityOptions options = ActivityOptions.makeBasic()
+                .setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_EMPTY);
+
+        final Intent[] intents = new Intent[] {
+                new Intent().setComponent(HANDLE_SPLASH_SCREEN_EXIT_ACTIVITY)
+                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                        .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true),
+                new Intent().setComponent(SPLASH_SCREEN_REPLACE_ICON_ACTIVITY)
+                        .putExtra(REQUEST_HANDLE_EXIT_ON_CREATE, true)
+        };
+        launchActivitiesFromLauncherWithOptions(intents, options,
+                SPLASH_SCREEN_REPLACE_ICON_ACTIVITY);
+        final TestJournalProvider.TestJournal journal =
+                TestJournalProvider.TestJournalContainer.get(REPLACE_ICON_EXIT);
+        assertFalse(journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+    }
+
+    private void assertReplaceIcon(TestJournalProvider.TestJournal journal) throws Exception {
+        TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+                () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+        assertTrue(journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+        final long iconAnimationStart = journal.extras.getLong(ICON_ANIMATION_START);
+        final long iconAnimationDuration = journal.extras.getLong(ICON_ANIMATION_DURATION);
+        assertTrue(iconAnimationStart != 0);
+        assertEquals(iconAnimationDuration, 500);
+        assertFalse(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+        assertTrue(journal.extras.getBoolean(CENTER_VIEW_IS_SURFACE_VIEW));
+    }
+
+    private void assertHandleExit(TestJournalProvider.TestJournal journal) throws Exception {
+        TestUtils.waitUntil("Waiting for runtime onSplashScreenExit", 5 /* timeoutSecond */,
+                () -> journal.extras.getBoolean(RECEIVE_SPLASH_SCREEN_EXIT));
+        assertTrue("No entry for CONTAINS_CENTER_VIEW",
+                journal.extras.containsKey(CONTAINS_CENTER_VIEW));
+        assertTrue("No entry for CONTAINS_BRANDING_VIEW",
+                journal.extras.containsKey(CONTAINS_BRANDING_VIEW));
+        assertTrue("Center View shouldn't be null",
+                journal.extras.getBoolean(CONTAINS_CENTER_VIEW));
+        assertTrue(journal.extras.getBoolean(CONTAINS_BRANDING_VIEW));
+        assertEquals(Color.BLUE, journal.extras.getInt(ICON_BACKGROUND_COLOR, Color.YELLOW));
+    }
+
+    @Test
     public void testOverrideSplashscreenTheme() {
         assumeFalse(isLeanBack());
         final CommandSession.ActivitySession homeActivity = prepareTestLauncher();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
index 7585856..9aed345 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -73,7 +73,10 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mOwnerActivity = startActivity(ActivityA.class);
+        // Launch activities in fullscreen, otherwise, some tests fail on devices which use freeform
+        // as the default windowing mode, because tests' prerequisite are that activity A, B, and C
+        // need to overlay completely, but they can be partially overlay as freeform windows.
+        mOwnerActivity = startActivityInWindowingModeFullScreen(ActivityA.class);
         mOwnerToken = getActivityToken(mOwnerActivity);
     }
 
@@ -561,7 +564,7 @@
     @Test
     public void testTranslucentAdjacentTaskFragment() {
         // Create ActivityB on top of ActivityA
-        Activity activityB = startActivity(ActivityB.class);
+        Activity activityB = startActivityInWindowingModeFullScreen(ActivityB.class);
         waitAndAssertResumedActivity(mActivityB, "Activity B must be resumed.");
         waitAndAssertActivityState(mActivityA, STATE_STOPPED,
                 "Activity A is occluded by Activity B, so it must be stopped.");
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
index 9e00b08..84a1900 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlTest.java
@@ -78,7 +78,7 @@
     }
 
     private void verifyTest(SurfaceHolder.Callback callback, PixelChecker pixelChecker) {
-        mActivity.verifyTest(callback, pixelChecker);
+        mActivity.verifyTest(callback, pixelChecker, mName);
     }
 
     @Before
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 12cc716..ade4a98 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -519,4 +519,57 @@
         assertTrue(mClicked);
     }
 
+    @Test
+    public void testCanReplaceSurfacePackage() throws Throwable {
+        // Create a surface view and wait for its surface to be created.
+        {
+            CountDownLatch surfaceCreated = new CountDownLatch(1);
+            mActivityRule.runOnUiThread(() -> {
+                final FrameLayout content = new FrameLayout(mActivity);
+                mSurfaceView = new SurfaceView(mActivity);
+                mSurfaceView.setZOrderOnTop(true);
+                content.addView(mSurfaceView, new FrameLayout.LayoutParams(
+                        DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT, 
+                        Gravity.LEFT | Gravity.TOP));
+                mActivity.setContentView(content, new ViewGroup.LayoutParams(
+                        DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT));
+                mSurfaceView.getHolder().addCallback(new SurfaceCreatedCallback(surfaceCreated));
+
+                // Create an embedded view without click handling.
+                mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+                        mSurfaceView.getHostToken());
+                mEmbeddedView = new Button(mActivity);
+                mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+
+            });
+            surfaceCreated.await();
+            mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage());
+            mInstrumentation.waitForIdleSync();
+            waitUntilEmbeddedViewDrawn();
+        }
+
+        {
+            CountDownLatch hostReady = new CountDownLatch(1);
+            // Create a second surface view and wait for its surface to be created.
+            mActivityRule.runOnUiThread(() -> {
+                // Create an embedded view.
+                mVr = new SurfaceControlViewHost(mActivity, mActivity.getDisplay(),
+                        mSurfaceView.getHostToken());
+                mEmbeddedView = new Button(mActivity);
+                mEmbeddedView.setOnClickListener((View v) -> mClicked = true);
+                mVr.setView(mEmbeddedView, mEmbeddedViewWidth, mEmbeddedViewHeight);
+                hostReady.countDown();
+
+            });
+            hostReady.await();
+            mSurfaceView.setChildSurfacePackage(mVr.getSurfacePackage());
+            mInstrumentation.waitForIdleSync();
+            waitUntilEmbeddedViewDrawn();
+        }
+
+        // Check to see if the click went through - this only would happen if the surface package
+        // was replaced
+        CtsTouchUtils.emulateTapOnViewCenter(mInstrumentation, mActivityRule, mSurfaceView);
+        assertTrue(mClicked);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
index 767650d..7e15846 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceViewTest.java
@@ -39,6 +39,7 @@
 import com.android.compatibility.common.util.CtsKeyEventUtil;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -61,7 +62,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mMockSurfaceView = mActivity.getSurfaceView();
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
index db90054..4741f39 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ToastWindowTest.java
@@ -19,10 +19,7 @@
 import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_DISPLAYED;
 import static android.server.wm.app.Components.ClickableToastActivity.ACTION_TOAST_TAP_DETECTED;
 
-import static com.google.common.truth.Truth.assertThat;
-
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -92,7 +89,7 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_TOAST_DISPLAYED);
         filter.addAction(ACTION_TOAST_TAP_DETECTED);
-        mContext.registerReceiver(mAppCommunicator, filter);
+        mContext.registerReceiver(mAppCommunicator, filter, Context.RECEIVER_EXPORTED);
     }
 
     @After
@@ -134,7 +131,7 @@
         WindowState toastWindow = wmState.findFirstWindowWithType(LayoutParams.TYPE_TOAST);
         assertNotNull("Couldn't retrieve toast window", toastWindow);
 
-        tapOnCenter(toastWindow.getContainingFrame(), toastWindow.getDisplayId());
+        tapOnCenter(toastWindow.getParentFrame(), toastWindow.getDisplayId());
 
         boolean toastClicked = getBroadcastReceivedVariable(ACTION_TOAST_TAP_DETECTED).block(
                 TOAST_TAP_TIMEOUT_MS);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
index 8e28903..20bce66 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -62,6 +62,7 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -96,7 +97,7 @@
         try (ImeSession imeSession = new ImeSession(SimpleIme.getName(mContext))) {
             TestActivity activity = launchActivity();
             activity.setUseControlApi(useControlApi);
-            PollingCheck.waitFor(activity::hasWindowFocus);
+            WindowUtil.waitForFocus(activity);
             activity.setEvaluator(() -> {
                 // This runs from time to time on the UI thread.
                 Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index 054da4c..0953fc5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -63,13 +63,10 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowInsetsAnimation;
-import android.view.WindowInsetsController;
 import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
@@ -521,7 +518,8 @@
 
     @Test
     public void testHideOnCreate() throws Exception {
-        final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
+        final TestHideOnCreateActivity activity =
+                startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
         ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT,
@@ -641,7 +639,8 @@
 
     @Test
     public void testWindowInsetsController_availableAfterAddView() throws Exception {
-        final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
+        final TestHideOnCreateActivity activity =
+                startActivityInWindowingModeFullScreen(TestHideOnCreateActivity.class);
         final View rootView = activity.getWindow().getDecorView();
         ANIMATION_CALLBACK.waitForFinishing();
         PollingCheck.waitFor(TIMEOUT,
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
index ab057d7..806cee5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsPolicyTest.java
@@ -17,6 +17,7 @@
 package android.server.wm;
 
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.provider.Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.view.Surface.ROTATION_0;
 import static android.view.Surface.ROTATION_180;
@@ -38,6 +39,8 @@
 import android.graphics.Insets;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.server.wm.settings.SettingsSession;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
@@ -47,10 +50,11 @@
 
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.Matcher;
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
@@ -65,6 +69,8 @@
 
     private ComponentName mTestActivityComponentName;
 
+    private SettingsSession<String> mImmersiveModeConfirmationSetting;
+
     @Rule
     public final ErrorCollector mErrorCollector = new ErrorCollector();
 
@@ -93,6 +99,18 @@
     public void setUp() throws Exception {
         super.setUp();
         mTestActivityComponentName = new ComponentName(mContext, TestActivity.class);
+        mImmersiveModeConfirmationSetting = new SettingsSession<>(
+                Settings.Secure.getUriFor(IMMERSIVE_MODE_CONFIRMATIONS),
+                Settings.Secure::getString, Settings.Secure::putString);
+        mImmersiveModeConfirmationSetting.set("confirmed");
+
+    }
+
+    @After
+    public void tearDown() {
+        if (mImmersiveModeConfirmationSetting != null) {
+            mImmersiveModeConfirmationSetting.close();
+        }
     }
 
     @Test
@@ -259,7 +277,7 @@
 
     private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule) {
         final T activity = rule.launchActivity(null);
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
         return activity;
     }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
index 7571a56..22ef041 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityStarterTests.java
@@ -32,7 +32,6 @@
 import static android.server.wm.app.Components.SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
 import static android.server.wm.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -40,22 +39,15 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
-import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.os.Bundle;
 import android.platform.test.annotations.Presubmit;
 import android.server.wm.ActivityLauncher;
-import android.server.wm.WindowManagerState;
 import android.server.wm.app.Components;
-import android.util.Log;
-import android.view.WindowManager;
 
 import org.junit.Test;
 
-import java.util.List;
-import java.util.function.Predicate;
-
 /**
  * Build/Install/Run:
  *     atest CtsWindowManagerDeviceTestCases:ActivityStarterTests
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 1c2fdf9..56cadf7 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
@@ -2499,7 +2499,8 @@
                 String amStartCmd =
                         (mWindowingMode == -1 || mNewTask)
                                 ? getAmStartCmd(mLaunchingActivity)
-                                : getAmStartCmd(mLaunchingActivity, mWindowingMode);
+                                : getAmStartCmd(mLaunchingActivity, mDisplayId)
+                                        + " --windowingMode " + mWindowingMode;
                 // Use launching activity to launch the target.
                 commandBuilder.append(amStartCmd)
                         .append(" -f 0x20000020");
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 b56b748..a98f546 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
@@ -403,7 +403,8 @@
             mThread = new HandlerThread(mClientId);
             mThread.start();
             context.registerReceiver(this, new IntentFilter(mClientId),
-                    null /* broadcastPermission */, new Handler(mThread.getLooper()));
+                    null /* broadcastPermission */, new Handler(mThread.getLooper()),
+                    Context.RECEIVER_EXPORTED);
         }
 
         /** Start the activity by the given intent and wait it becomes idle. */
@@ -558,7 +559,7 @@
             mHostId = hostId;
             mClientId = clientId;
             mCallback = callback;
-            context.registerReceiver(this, new IntentFilter(hostId));
+            context.registerReceiver(this, new IntentFilter(hostId), Context.RECEIVER_EXPORTED);
         }
 
         @Override
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 2308932..fd21f5b 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
@@ -45,7 +45,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.SystemClock;
 import android.util.SparseArray;
-import android.view.WindowManager;
 import android.view.nano.DisplayInfoProto;
 import android.view.nano.ViewProtoEnums;
 
@@ -1095,6 +1094,10 @@
         return mFocusedDisplayId;
     }
 
+    public boolean isFixedToUserRotation() {
+        return getDisplay(DEFAULT_DISPLAY).mIsFixedToUserRotation;
+    }
+
     public static class DisplayContent extends DisplayArea {
         public int mId;
         ArrayList<Task> mRootTasks = new ArrayList<>();
@@ -1118,6 +1121,7 @@
         private int mUserRotation;
         private int mFixedToUserRotationMode;
         private int mLastOrientation;
+        private boolean mIsFixedToUserRotation;
 
         DisplayContent(DisplayContentProto proto) {
             super(proto.rootDisplayArea);
@@ -1164,6 +1168,7 @@
                 mUserRotation = rotationProto.userRotation;
                 mFixedToUserRotationMode = rotationProto.fixedToUserRotationMode;
                 mLastOrientation = rotationProto.lastOrientation;
+                mIsFixedToUserRotation = rotationProto.isFixedToUserRotation;
             }
         }
 
@@ -1288,6 +1293,64 @@
             return "Display #" + mId + ": name=" + mName + " mDisplayRect=" + mDisplayRect
                     + " mAppRect=" + mAppRect + " mFlags=" + mFlags;
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == this) {
+                return true;
+            }
+            if (o == null) {
+                return false;
+            }
+            if (!(o instanceof DisplayContent)) {
+                return false;
+            }
+
+            DisplayContent dc = (DisplayContent) o;
+
+            return (dc.mDisplayRect == null ? mDisplayRect == null
+                    : dc.mDisplayRect.equals(mDisplayRect))
+                && (dc.mAppRect == null ? mAppRect == null : dc.mAppRect.equals(mAppRect))
+                && dc.mDpi == mDpi
+                && dc.mFlags == mFlags
+                && (dc.mName == null ? mName == null : dc.mName.equals(mName))
+                && dc.mSurfaceSize == mSurfaceSize
+                && (dc.mAppTransitionState == null ? mAppTransitionState == null
+                    : dc.mAppTransitionState.equals(mAppTransitionState))
+                && dc.mRotation == mRotation
+                && dc.mFrozenToUserRotation == mFrozenToUserRotation
+                && dc.mUserRotation == mUserRotation
+                && dc.mFixedToUserRotationMode == mFixedToUserRotationMode
+                && dc.mLastOrientation == mLastOrientation
+                && dc.mIsFixedToUserRotation == mIsFixedToUserRotation;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 0;
+            if (mDisplayRect != null) {
+                result = 31 * result + mDisplayRect.hashCode();
+            }
+            if (mAppRect != null) {
+                result = 31 * result + mAppRect.hashCode();
+            }
+            result = 31 * result + mDpi;
+            result = 31 * result + mFlags;
+            if (mName != null) {
+                result = 31 * result + mName.hashCode();
+            }
+            result = 31 * result + mSurfaceSize;
+            if (mAppTransitionState != null) {
+                result = 31 * result + mAppTransitionState.hashCode();
+            }
+            result = 31 * result + mRotation;
+            result = 31 * result + Boolean.hashCode(mFrozenToUserRotation);
+            result = 31 * result + mUserRotation;
+            result = 31 * result + mFixedToUserRotationMode;
+            result = 31 * result + mLastOrientation;
+            result = 31 * result + Boolean.hashCode(mIsFixedToUserRotation);
+            return result;
+        }
     }
 
     public static class Task extends ActivityContainer {
@@ -1656,9 +1719,9 @@
             if (proto != null) {
                 aodShowing = proto.aodShowing;
                 keyguardShowing = proto.keyguardShowing;
-                for (int i = 0;  i < proto.keyguardOccludedStates.length; i++) {
-                    mKeyguardOccludedStates.append(proto.keyguardOccludedStates[i].displayId,
-                            proto.keyguardOccludedStates[i].keyguardOccluded);
+                for (int i = 0;  i < proto.keyguardPerDisplay.length; i++) {
+                    mKeyguardOccludedStates.append(proto.keyguardPerDisplay[i].displayId,
+                            proto.keyguardPerDisplay[i].keyguardOccluded);
                 }
             }
         }
@@ -1904,12 +1967,11 @@
         private int mStackId;
         private int mLayer;
         private boolean mShown;
-        private Rect mContainingFrame;
         private Rect mParentFrame;
         private Rect mFrame;
         private Rect mCompatFrame;
-        private Rect mSurfaceInsets = new Rect();
-        private Rect mGivenContentInsets = new Rect();
+        private Rect mSurfaceInsets;
+        private Rect mGivenContentInsets;
         private Rect mCrop = new Rect();
         private boolean mHasCompatScale;
         private float mGlobalScale;
@@ -1936,7 +1998,6 @@
             WindowFramesProto windowFramesProto = proto.windowFrames;
             if (windowFramesProto != null) {
                 mFrame = extract(windowFramesProto.frame);
-                mContainingFrame = extract(windowFramesProto.containingFrame);
                 mParentFrame = extract(windowFramesProto.parentFrame);
                 mCompatFrame = extract(windowFramesProto.compatFrame);
             }
@@ -1980,10 +2041,6 @@
             return mStackId;
         }
 
-        Rect getContainingFrame() {
-            return mContainingFrame;
-        }
-
         public Rect getFrame() {
             return mFrame;
         }
@@ -2050,7 +2107,7 @@
         public String toString() {
             return "WindowState: {" + mAppToken + " " + mName
                     + getWindowTypeSuffix(mWindowType) + "}" + " type=" + mType
-                    + " cf=" + mContainingFrame + " pf=" + mParentFrame;
+                    + " pf=" + mParentFrame;
         }
 
         public String toLongString() {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 0f0e512..42f5e92 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -227,16 +227,13 @@
     }
 
     /**
-     * Wait for orientation for the Activity
+     * Wait for the configuration orientation of the Activity.
      */
-    public void waitForActivityOrientation(ComponentName activityName, int orientation) {
-        waitForWithAmState(amState -> {
-            final Task task = amState.getTaskByActivity(activityName);
-            if (task == null) {
-                return false;
-            }
-            return task.mFullConfiguration.orientation == orientation;
-        }, "orientation of " + getActivityName(activityName) + " to be " + orientation);
+    public boolean waitForActivityOrientation(ComponentName activityName, int configOrientation) {
+        return waitForWithAmState(amState -> {
+            final Activity activity = amState.getActivity(activityName);
+            return activity != null && activity.mFullConfiguration.orientation == configOrientation;
+        }, "orientation of " + getActivityName(activityName) + " to be " + configOrientation);
     }
 
     public void waitForDisplayUnfrozen() {
@@ -320,7 +317,7 @@
         }, windowName + "'s surface is disappeared");
     }
 
-    void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
+    public void waitAndAssertWindowSurfaceShown(String windowName, boolean shown) {
         assertTrue(
                 waitForWithAmState(state -> state.isWindowSurfaceShown(windowName) == shown,
                         windowName + "'s  isWindowSurfaceShown to return " + shown));
diff --git a/tests/input/AndroidManifest.xml b/tests/input/AndroidManifest.xml
index f019c8f..666ec20 100644
--- a/tests/input/AndroidManifest.xml
+++ b/tests/input/AndroidManifest.xml
@@ -39,11 +39,14 @@
 
         <activity android:name="android.input.cts.IncompleteMotionActivity"
                   android:label="IncompleteMotion activity"
-                  android:turnScreenOn="true">
+                  android:turnScreenOn="true"
+                  android:exported="true">
         </activity>
         <activity android:name="android.input.cts.CaptureEventActivity"
                   android:label="Capture events"
-                  android:turnScreenOn="true">
+                  android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|uiMode|navigation|keyboard|density|fontScale|layoutDirection|locale|mcc|mnc|smallestScreenSize"
+                  android:turnScreenOn="true"
+                  android:exported="true">
         </activity>
     </application>
 
diff --git a/tests/input/res/raw/test_keyboard_register.json b/tests/input/res/raw/test_keyboard_register.json
new file mode 100644
index 0000000..bc2e4a4
--- /dev/null
+++ b/tests/input/res/raw/test_keyboard_register.json
@@ -0,0 +1,14 @@
+{
+    "id": 1,
+    "type": "uinput",
+    "command": "register",
+    "name": "Test Keyboard (USB)",
+    "vid": 0x18d1,
+    "pid": 0xabcd,
+    "bus": "usb",
+    "configuration":[
+        {"type": 100, "data": [1]},  // UI_SET_EVBIT : EV_KEY
+        // UI_SET_KEYBIT : KEY_Q, KEY_W, KEY_E, KEY_A, KEY_B, KEY_C
+        {"type": 101, "data": [16, 17, 18, 30, 48, 46]}
+    ]
+}
diff --git a/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt b/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt
new file mode 100644
index 0000000..145c36f
--- /dev/null
+++ b/tests/input/src/android/input/cts/AppKeyCombinationsTest.kt
@@ -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 android.input.cts
+
+import android.view.KeyEvent
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import com.android.compatibility.common.util.ShellUtils
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.Assert.assertEquals
+
+/**
+ * Tests for the common key combinations should be consumed by app first.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AppKeyCombinationsTest {
+    @get:Rule
+    var activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+    private lateinit var activity: CaptureEventActivity
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @Before
+    fun setUp() {
+        activityRule.getScenario().onActivity {
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+        instrumentation.uiAutomation.syncInputTransactions()
+    }
+
+    @Test
+    fun testCtrlSpace() {
+        ShellUtils.runShellCommand("input keycombination CTRL_LEFT SPACE")
+        assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_DOWN, 0)
+        assertKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_DOWN, META_CTRL)
+        assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_UP, META_CTRL)
+        assertKeyEvent(KeyEvent.KEYCODE_SPACE, KeyEvent.ACTION_UP, 0)
+    }
+
+    @Test
+    fun testCtrlAltZ() {
+        ShellUtils.runShellCommand("input keycombination CTRL_LEFT ALT_LEFT Z")
+        assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_DOWN, 0)
+        assertKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.ACTION_DOWN, META_CTRL)
+        assertKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.ACTION_DOWN, META_CTRL or META_ALT)
+        assertKeyEvent(KeyEvent.KEYCODE_CTRL_LEFT, KeyEvent.ACTION_UP, META_CTRL or META_ALT)
+        assertKeyEvent(KeyEvent.KEYCODE_ALT_LEFT, KeyEvent.ACTION_UP, META_ALT)
+        assertKeyEvent(KeyEvent.KEYCODE_Z, KeyEvent.ACTION_UP, 0)
+    }
+
+    private fun assertKeyEvent(keyCode: Int, action: Int, metaState: Int) {
+        val event = activity.getInputEvent() as KeyEvent
+        assertEquals(keyCode, event.keyCode)
+        assertEquals(action, event.action)
+        assertEquals(0, event.flags)
+        assertEquals(metaState, event.metaState)
+    }
+
+    companion object {
+        const val META_CTRL = KeyEvent.META_CTRL_ON or KeyEvent.META_CTRL_LEFT_ON
+        const val META_ALT = KeyEvent.META_ALT_ON or KeyEvent.META_ALT_LEFT_ON
+    }
+}
diff --git a/tests/input/src/android/input/cts/CaptureEventActivity.kt b/tests/input/src/android/input/cts/CaptureEventActivity.kt
index 4f5caad..73c911d 100644
--- a/tests/input/src/android/input/cts/CaptureEventActivity.kt
+++ b/tests/input/src/android/input/cts/CaptureEventActivity.kt
@@ -24,29 +24,29 @@
 import java.util.concurrent.TimeUnit
 
 class CaptureEventActivity : Activity() {
-    private val mEvents = LinkedBlockingQueue<InputEvent>()
+    private val events = LinkedBlockingQueue<InputEvent>()
 
     override fun dispatchGenericMotionEvent(ev: MotionEvent?): Boolean {
-        mEvents.add(MotionEvent.obtain(ev))
+        events.add(MotionEvent.obtain(ev))
         return true
     }
 
     override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
-        mEvents.add(MotionEvent.obtain(ev))
+        events.add(MotionEvent.obtain(ev))
         return true
     }
 
     override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
-        mEvents.add(KeyEvent(event))
+        events.add(KeyEvent(event))
         return true
     }
 
     override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
-        mEvents.add(MotionEvent.obtain(ev))
+        events.add(MotionEvent.obtain(ev))
         return true
     }
 
-    fun getLastInputEvent(): InputEvent? {
-        return mEvents.poll(5, TimeUnit.SECONDS)
+    fun getInputEvent(): InputEvent? {
+        return events.poll(5, TimeUnit.SECONDS)
     }
 }
diff --git a/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt b/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
index 4b8adaa..7fbac02 100644
--- a/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
+++ b/tests/input/src/android/input/cts/GamepadWithAccessibilityTest.kt
@@ -22,11 +22,12 @@
 import android.view.InputDevice
 import android.view.KeyCharacterMap.VIRTUAL_KEYBOARD
 import android.view.KeyEvent
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.PollingCheck
+import com.android.cts.input.UinputDevice
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -37,12 +38,9 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-import com.android.cts.input.InputJsonParser
-import com.android.cts.input.UinputDevice
-
 private fun setTouchExplorationEnabled(instrumentation: Instrumentation, enabled: Boolean) {
         val TIMEOUT_FOR_SERVICE_ENABLE_MILLIS: Long = 10_000 // 10s
-        var manager: AccessibilityManager =
+        val manager: AccessibilityManager =
             instrumentation.getTargetContext().getSystemService(AccessibilityManager::class.java)
 
         val uiAutomation = instrumentation.getUiAutomation()
@@ -70,21 +68,22 @@
 @RunWith(AndroidJUnit4::class)
 class GamepadWithAccessibilityTest {
     @get:Rule
-    var mActivityRule: ActivityTestRule<CaptureEventActivity> =
-            ActivityTestRule(CaptureEventActivity::class.java)
-    lateinit var mActivity: CaptureEventActivity
-    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+    val activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+    private lateinit var activity: CaptureEventActivity
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
 
     @Before
     fun setUp() {
-        mActivity = mActivityRule.getActivity()
-        PollingCheck.waitFor { mActivity.hasWindowFocus() }
-        setTouchExplorationEnabled(mInstrumentation, true)
+        activityRule.getScenario().onActivity {
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+        setTouchExplorationEnabled(instrumentation, true)
     }
 
     @After
     fun tearDown() {
-        setTouchExplorationEnabled(mInstrumentation, false)
+        setTouchExplorationEnabled(instrumentation, false)
     }
 
     /**
@@ -93,14 +92,8 @@
      */
     @Test
     fun testDeviceId() {
-        val resourceId = R.raw.google_gamepad_register
-
-        val parser = InputJsonParser(mInstrumentation.getTargetContext())
-        val resourceDeviceId = parser.readDeviceId(resourceId)
-        val registerCommand = parser.readRegisterCommand(resourceId)
-        val uInputDevice = UinputDevice(mInstrumentation, resourceDeviceId,
-                parser.readVendorId(resourceId), parser.readProductId(resourceId),
-                InputDevice.SOURCE_KEYBOARD, registerCommand)
+        val uinputDevice = UinputDevice.create(instrumentation, R.raw.google_gamepad_register,
+                InputDevice.SOURCE_KEYBOARD)
 
         val EV_SYN = 0
         val SYN_REPORT = 0
@@ -109,14 +102,14 @@
         val EV_KEY_UP = 0
         val BTN_GAMEPAD = 0x130
         val evdevEventsDown = intArrayOf(EV_KEY, BTN_GAMEPAD, EV_KEY_DOWN, EV_SYN, SYN_REPORT, 0)
-        uInputDevice.injectEvents(evdevEventsDown.joinToString(prefix = "[", postfix = "]",
+        uinputDevice.injectEvents(evdevEventsDown.joinToString(prefix = "[", postfix = "]",
                 separator = ","))
 
         val evdevEventsUp = intArrayOf(EV_KEY, BTN_GAMEPAD, EV_KEY_UP, EV_SYN, SYN_REPORT, 0)
-        uInputDevice.injectEvents(evdevEventsUp.joinToString(prefix = "[", postfix = "]",
+        uinputDevice.injectEvents(evdevEventsUp.joinToString(prefix = "[", postfix = "]",
                 separator = ","))
 
-        val lastInputEvent = mActivity.getLastInputEvent()
+        val lastInputEvent = activity.getInputEvent()
         assertNotNull(lastInputEvent)
         assertTrue(lastInputEvent is KeyEvent)
         val keyEvent = lastInputEvent as KeyEvent
@@ -124,6 +117,6 @@
         // KeyEvent.FLAG_IS_ACCESSIBILITY_EVENT in getFlags()
         assertEquals(KeyEvent.FLAG_FROM_SYSTEM, keyEvent.getFlags())
         assertNotEquals(keyEvent.getDeviceId(), VIRTUAL_KEYBOARD)
-        assertEquals(keyEvent.getDeviceId(), uInputDevice.getDeviceId())
+        assertEquals(keyEvent.getDeviceId(), uinputDevice.getDeviceId())
     }
 }
diff --git a/tests/input/src/android/input/cts/IncompleteMotionActivity.kt b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
index ac7cc3b..706619d 100644
--- a/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
+++ b/tests/input/src/android/input/cts/IncompleteMotionActivity.kt
@@ -21,15 +21,15 @@
 import java.util.concurrent.atomic.AtomicBoolean
 
 class IncompleteMotionActivity : Activity() {
-    private val mReceivedMove = AtomicBoolean(false)
+    private val receivedMove = AtomicBoolean(false)
     override fun onTouchEvent(event: MotionEvent): Boolean {
         if (event.action == MotionEvent.ACTION_MOVE) {
-            mReceivedMove.set(true)
+            receivedMove.set(true)
         }
         return true
     }
 
     fun receivedMove(): Boolean {
-        return mReceivedMove.get()
+        return receivedMove.get()
     }
 }
diff --git a/tests/input/src/android/input/cts/IncompleteMotionTest.kt b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
index 26ae69a..d39958e 100644
--- a/tests/input/src/android/input/cts/IncompleteMotionTest.kt
+++ b/tests/input/src/android/input/cts/IncompleteMotionTest.kt
@@ -28,10 +28,10 @@
 import android.view.MotionEvent.ACTION_DOWN
 import android.view.MotionEvent.ACTION_MOVE
 import android.view.View
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.PollingCheck
 import org.junit.Before
 import org.junit.Rule
@@ -54,13 +54,13 @@
  * When OverlayActivity receives focus, it will send out the OVERLAY_ACTIVITY_FOCUSED broadcast.
  */
 class OverlayFocusedBroadcastReceiver : BroadcastReceiver() {
-    private val mIsFocused = AtomicBoolean(false)
+    private val isFocused = AtomicBoolean(false)
     override fun onReceive(context: Context, intent: Intent) {
-        mIsFocused.set(true)
+        isFocused.set(true)
     }
 
     fun overlayActivityIsFocused(): Boolean {
-        return mIsFocused.get()
+        return isFocused.get()
     }
 }
 
@@ -78,15 +78,16 @@
 @RunWith(AndroidJUnit4::class)
 class IncompleteMotionTest {
     @get:Rule
-    var mActivityRule: ActivityTestRule<IncompleteMotionActivity> =
-            ActivityTestRule(IncompleteMotionActivity::class.java)
-    lateinit var mActivity: IncompleteMotionActivity
-    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+    val activityRule = ActivityScenarioRule(IncompleteMotionActivity::class.java)
+    private lateinit var activity: IncompleteMotionActivity
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
 
     @Before
     fun setUp() {
-        mActivity = mActivityRule.getActivity()
-        PollingCheck.waitFor { mActivity.hasWindowFocus() }
+        activityRule.getScenario().onActivity {
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
     }
 
     /**
@@ -95,13 +96,13 @@
     @Test
     fun testIncompleteMotion() {
         val downTime = SystemClock.uptimeMillis()
-        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+        val (x, y) = getViewCenterOnScreen(activity.window.decorView)
 
         // Start a valid touch stream
         sendEvent(downTime, ACTION_DOWN, x, y, true /*sync*/)
         // Lock up the UI thread. This ensures that the motion event that we will write will
         // not get processed by the app right away.
-        mActivity.runOnUiThread {
+        activity.runOnUiThread {
             val sendMoveAndFocus = thread(start = true) {
                 sendEvent(downTime, ACTION_MOVE, x, y + 10, false /*sync*/)
                 // The MOVE event is sent async because the UI thread is blocked.
@@ -114,12 +115,12 @@
                 val handler = Handler(looper)
                 val receiver = OverlayFocusedBroadcastReceiver()
                 val intentFilter = IntentFilter(OVERLAY_ACTIVITY_FOCUSED)
-                mActivity.registerReceiver(receiver, intentFilter, null, handler)
+                activity.registerReceiver(receiver, intentFilter, null, handler)
 
                 // Now send hasFocus=false event to the app by launching a new focusable window
                 startOverlayActivity()
                 PollingCheck.waitFor { receiver.overlayActivityIsFocused() }
-                mActivity.unregisterReceiver(receiver)
+                activity.unregisterReceiver(receiver)
                 handlerThread.quit()
                 // We need to ensure that the focus event has been written to the app's socket
                 // before unblocking the UI thread. Having the overlay activity receive
@@ -130,14 +131,14 @@
             }
             sendMoveAndFocus.join()
         }
-        PollingCheck.waitFor { !mActivity.hasWindowFocus() }
+        PollingCheck.waitFor { !activity.hasWindowFocus() }
         // If the platform implementation has a bug, it would consume both MOVE and FOCUS events,
         // but will only call 'finish' for the focus event.
         // The MOVE event would not be propagated to the app, because the Choreographer
         // callback never gets scheduled
         // If we wait too long here, we will cause ANR (if the platform has a bug).
         // If the MOVE event is received, however, we can stop the test.
-        PollingCheck.waitFor { mActivity.receivedMove() }
+        PollingCheck.waitFor { activity.receivedMove() }
     }
 
     private fun sendEvent(downTime: Long, action: Int, x: Float, y: Float, sync: Boolean) {
@@ -147,7 +148,7 @@
         }
         val event = MotionEvent.obtain(downTime, eventTime, action, x, y, 0 /*metaState*/)
         event.source = InputDevice.SOURCE_TOUCHSCREEN
-        mInstrumentation.uiAutomation.injectInputEvent(event, sync)
+        instrumentation.uiAutomation.injectInputEvent(event, sync)
     }
 
     /**
@@ -161,6 +162,6 @@
     private fun startOverlayActivity() {
         val flags = " -W -n "
         val startCmd = "am start $flags android.input.cts/.OverlayActivity"
-        mInstrumentation.uiAutomation.executeShellCommand(startCmd)
+        instrumentation.uiAutomation.executeShellCommand(startCmd)
     }
 }
diff --git a/tests/input/src/android/input/cts/InputEventTest.kt b/tests/input/src/android/input/cts/InputEventTest.kt
new file mode 100644
index 0000000..fd20ac2
--- /dev/null
+++ b/tests/input/src/android/input/cts/InputEventTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.input.cts
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+
+import android.text.TextUtils
+import android.util.ArrayMap
+import android.view.KeyEvent
+import android.view.MotionEvent
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class InputEventTest {
+
+    @Test
+    fun testKeyCodeToString() {
+        assertEquals("KEYCODE_UNKNOWN", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_UNKNOWN))
+        assertEquals("KEYCODE_HOME", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_HOME))
+        assertEquals("KEYCODE_0", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_0))
+        assertEquals("KEYCODE_POWER", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_POWER))
+        assertEquals("KEYCODE_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_A))
+        assertEquals("KEYCODE_SPACE", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_SPACE))
+        assertEquals("KEYCODE_MENU", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_MENU))
+        assertEquals("KEYCODE_BACK", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BACK))
+        assertEquals("KEYCODE_BUTTON_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BUTTON_A))
+        assertEquals("KEYCODE_PROFILE_SWITCH",
+                        KeyEvent.keyCodeToString(KeyEvent.KEYCODE_PROFILE_SWITCH))
+    }
+
+    @Test
+    fun testAxisFromToString() {
+        val axes = ArrayMap<Int, String>()
+        axes.put(MotionEvent.AXIS_X, "AXIS_X")
+        axes.put(MotionEvent.AXIS_Y, "AXIS_Y")
+        axes.put(MotionEvent.AXIS_PRESSURE, "AXIS_PRESSURE")
+        axes.put(MotionEvent.AXIS_SIZE, "AXIS_SIZE")
+        axes.put(MotionEvent.AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR")
+        axes.put(MotionEvent.AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR")
+        axes.put(MotionEvent.AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR")
+        axes.put(MotionEvent.AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR")
+        axes.put(MotionEvent.AXIS_ORIENTATION, "AXIS_ORIENTATION")
+        axes.put(MotionEvent.AXIS_VSCROLL, "AXIS_VSCROLL")
+        axes.put(MotionEvent.AXIS_HSCROLL, "AXIS_HSCROLL")
+        axes.put(MotionEvent.AXIS_Z, "AXIS_Z")
+        axes.put(MotionEvent.AXIS_RX, "AXIS_RX")
+        axes.put(MotionEvent.AXIS_RY, "AXIS_RY")
+        axes.put(MotionEvent.AXIS_RZ, "AXIS_RZ")
+        axes.put(MotionEvent.AXIS_HAT_X, "AXIS_HAT_X")
+        axes.put(MotionEvent.AXIS_HAT_Y, "AXIS_HAT_Y")
+        axes.put(MotionEvent.AXIS_LTRIGGER, "AXIS_LTRIGGER")
+        axes.put(MotionEvent.AXIS_RTRIGGER, "AXIS_RTRIGGER")
+        axes.put(MotionEvent.AXIS_THROTTLE, "AXIS_THROTTLE")
+        axes.put(MotionEvent.AXIS_RUDDER, "AXIS_RUDDER")
+        axes.put(MotionEvent.AXIS_WHEEL, "AXIS_WHEEL")
+        axes.put(MotionEvent.AXIS_GAS, "AXIS_GAS")
+        axes.put(MotionEvent.AXIS_BRAKE, "AXIS_BRAKE")
+        axes.put(MotionEvent.AXIS_DISTANCE, "AXIS_DISTANCE")
+        axes.put(MotionEvent.AXIS_TILT, "AXIS_TILT")
+        axes.put(MotionEvent.AXIS_SCROLL, "AXIS_SCROLL")
+        axes.put(MotionEvent.AXIS_RELATIVE_X, "AXIS_RELATIVE_X")
+        axes.put(MotionEvent.AXIS_RELATIVE_Y, "AXIS_RELATIVE_Y")
+        axes.put(MotionEvent.AXIS_GENERIC_1, "AXIS_GENERIC_1")
+        axes.put(MotionEvent.AXIS_GENERIC_2, "AXIS_GENERIC_2")
+        axes.put(MotionEvent.AXIS_GENERIC_3, "AXIS_GENERIC_3")
+        axes.put(MotionEvent.AXIS_GENERIC_4, "AXIS_GENERIC_4")
+        axes.put(MotionEvent.AXIS_GENERIC_5, "AXIS_GENERIC_5")
+        axes.put(MotionEvent.AXIS_GENERIC_6, "AXIS_GENERIC_6")
+        axes.put(MotionEvent.AXIS_GENERIC_7, "AXIS_GENERIC_7")
+        axes.put(MotionEvent.AXIS_GENERIC_8, "AXIS_GENERIC_8")
+        axes.put(MotionEvent.AXIS_GENERIC_9, "AXIS_GENERIC_9")
+        axes.put(MotionEvent.AXIS_GENERIC_10, "AXIS_GENERIC_10")
+        axes.put(MotionEvent.AXIS_GENERIC_11, "AXIS_GENERIC_11")
+        axes.put(MotionEvent.AXIS_GENERIC_12, "AXIS_GENERIC_12")
+        axes.put(MotionEvent.AXIS_GENERIC_13, "AXIS_GENERIC_13")
+        axes.put(MotionEvent.AXIS_GENERIC_14, "AXIS_GENERIC_14")
+        axes.put(MotionEvent.AXIS_GENERIC_15, "AXIS_GENERIC_15")
+        axes.put(MotionEvent.AXIS_GENERIC_16, "AXIS_GENERIC_16")
+        // As Axes values definition is not continuous from AXIS_RELATIVE_Y to AXIS_GENERIC_1,
+        // Need to verify MotionEvent.axisToString returns axis name correctly.
+        // Also verify that we are not crashing on those calls, and that the return result on each
+        // is not empty. We do expect the two-way call chain of to/from to get us back to the
+        // original integer value.
+        for (entry in axes.entries) {
+            val axis = entry.key
+            val axisToString = MotionEvent.axisToString(axis)
+            assertFalse(TextUtils.isEmpty(axisToString))
+            assertEquals(axisToString, entry.value)
+            assertEquals(axis, MotionEvent.axisFromString(axisToString))
+        }
+    }
+}
diff --git a/tests/input/src/android/input/cts/InputShellCommandTest.kt b/tests/input/src/android/input/cts/InputShellCommandTest.kt
index 4555b5d..c075204 100644
--- a/tests/input/src/android/input/cts/InputShellCommandTest.kt
+++ b/tests/input/src/android/input/cts/InputShellCommandTest.kt
@@ -17,10 +17,10 @@
 
 import android.view.MotionEvent
 import android.view.View
+import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.PollingCheck
 import com.android.compatibility.common.util.ShellUtils
 import com.google.common.truth.Truth.assertThat
@@ -44,15 +44,17 @@
 @RunWith(AndroidJUnit4::class)
 class InputShellCommandTest {
     @get:Rule
-    var mActivityRule: ActivityTestRule<CaptureEventActivity> =
-            ActivityTestRule(CaptureEventActivity::class.java)
-    lateinit var mActivity: CaptureEventActivity
-    val mInstrumentation = InstrumentationRegistry.getInstrumentation()
+    val activityRule = ActivityScenarioRule(CaptureEventActivity::class.java)
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private lateinit var activity: CaptureEventActivity
 
     @Before
     fun setUp() {
-        mActivity = mActivityRule.getActivity()
-        PollingCheck.waitFor { mActivity.hasWindowFocus() }
+        activityRule.getScenario().onActivity {
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+        instrumentation.uiAutomation.syncInputTransactions()
     }
 
     /**
@@ -60,7 +62,7 @@
      */
     @Test
     fun testDefaultToolType() {
-        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+        val (x, y) = getViewCenterOnScreen(activity.window.decorView)
 
         ShellUtils.runShellCommand("input tap $x $y")
         assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
@@ -71,7 +73,7 @@
      */
     @Test
     fun testToolType() {
-        val (x, y) = getViewCenterOnScreen(mActivity.window.decorView)
+        val (x, y) = getViewCenterOnScreen(activity.window.decorView)
 
         ShellUtils.runShellCommand("input touchscreen tap $x $y")
         assertTapToolType(MotionEvent.TOOL_TYPE_FINGER)
@@ -96,7 +98,7 @@
     }
 
     private fun getMotionEvent(): MotionEvent {
-        val event = mActivity.getLastInputEvent()
+        val event = activity.getInputEvent()
         assertThat(event).isNotNull()
         assertThat(event).isInstanceOf(MotionEvent::class.java)
         return event as MotionEvent
@@ -111,12 +113,12 @@
     }
 
     private fun assertTapToolType(toolType: Int) {
-        var event = getMotionEvent()
-        assertThat(event.action).isEqualTo(MotionEvent.ACTION_DOWN)
-        assertToolType(event, toolType)
+        val downEvent = getMotionEvent()
+        assertThat(downEvent.action).isEqualTo(MotionEvent.ACTION_DOWN)
+        assertToolType(downEvent, toolType)
 
-        event = getMotionEvent()
-        assertThat(event.action).isEqualTo(MotionEvent.ACTION_UP)
-        assertToolType(event, toolType)
+        val upEvent = getMotionEvent()
+        assertThat(upEvent.action).isEqualTo(MotionEvent.ACTION_UP)
+        assertToolType(upEvent, toolType)
     }
 }
diff --git a/tests/input/src/android/input/cts/PointerCancelTest.kt b/tests/input/src/android/input/cts/PointerCancelTest.kt
new file mode 100644
index 0000000..af7ca56
--- /dev/null
+++ b/tests/input/src/android/input/cts/PointerCancelTest.kt
@@ -0,0 +1,240 @@
+/*
+ * 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.input.cts
+
+import android.graphics.PointF
+import android.os.SystemClock
+import android.view.Gravity
+import android.view.InputDevice
+import android.view.InputEvent
+import android.view.MotionEvent
+import android.view.View
+import android.view.WindowManager
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.util.concurrent.LinkedBlockingQueue
+import java.util.concurrent.TimeUnit
+
+private fun getViewCenterOnScreen(v: View): PointF {
+    val location = IntArray(2)
+    v.getLocationOnScreen(location)
+    val x = location[0].toFloat() + v.width / 2
+    val y = location[1].toFloat() + v.height / 2
+    return PointF(x, y)
+}
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class PointerCancelTest {
+    @get:Rule
+    val activityRule = ActivityScenarioRule<CaptureEventActivity>(CaptureEventActivity::class.java)
+    private lateinit var activity: CaptureEventActivity
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private lateinit var verifier: EventVerifier
+    @Before
+    fun setUp() {
+        activityRule.getScenario().onActivity {
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+        instrumentation.uiAutomation.syncInputTransactions()
+        verifier = EventVerifier(activity::getInputEvent)
+    }
+
+    /**
+     * Check that pointer cancel is received by the activity via injectInputEvent.
+     */
+    @Test
+    fun testPointerCancelMotion() {
+        val downTime = SystemClock.uptimeMillis()
+        val pointerInDecorView = getViewCenterOnScreen(activity.window.decorView)
+
+        // Start a valid touch stream
+        sendEvent(downTime, MotionEvent.ACTION_DOWN, pointerInDecorView, true /*sync*/)
+        verifier.assertReceivedDown()
+
+        pointerInDecorView.offset(0f, 1f)
+        sendEvent(downTime, MotionEvent.ACTION_MOVE, pointerInDecorView, true /*sync*/)
+        verifier.assertReceivedMove()
+
+        val secondPointer = PointF(pointerInDecorView.x + 1, pointerInDecorView.y + 1)
+        sendPointersEvent(downTime, ACTION_POINTER_1_DOWN,
+                arrayOf(pointerInDecorView, secondPointer),
+                0 /*flags*/, true /*sync*/)
+        verifier.assertReceivedPointerDown()
+
+        sendPointersEvent(downTime, ACTION_POINTER_1_UP,
+                arrayOf(pointerInDecorView, secondPointer),
+                MotionEvent.FLAG_CANCELED, true /*sync*/)
+        verifier.assertReceivedPointerCancel()
+
+        sendEvent(downTime, MotionEvent.ACTION_UP, pointerInDecorView, true /*sync*/)
+        verifier.assertReceivedUp()
+    }
+
+    @Test
+    fun testPointerCancelForSplitTouch() {
+        val view = addFloatingWindow()
+        val pointerInFloating = getViewCenterOnScreen(view)
+        val downTime = SystemClock.uptimeMillis()
+        val pointerOutsideFloating = PointF(pointerInFloating.x + view.width / 2 + 1,
+                pointerInFloating.y + view.height / 2 + 1)
+
+        val eventsInFloating = LinkedBlockingQueue<InputEvent>()
+        view.setOnTouchListener { v, event ->
+            eventsInFloating.add(MotionEvent.obtain(event))
+        }
+        val verifierForFloating = EventVerifier { eventsInFloating.poll(5, TimeUnit.SECONDS) }
+
+        // First finger down (floating window)
+        sendEvent(downTime, MotionEvent.ACTION_DOWN, pointerInFloating, true /*sync*/)
+        verifierForFloating.assertReceivedDown()
+
+        // First finger move (floating window)
+        pointerInFloating.offset(0f, 1f)
+        sendEvent(downTime, MotionEvent.ACTION_MOVE, pointerInFloating, true /*sync*/)
+        verifierForFloating.assertReceivedMove()
+
+        // Second finger down (activity window)
+        sendPointersEvent(downTime, ACTION_POINTER_1_DOWN,
+                arrayOf(pointerInFloating, pointerOutsideFloating),
+                0 /*flags*/, true /*sync*/)
+        verifier.assertReceivedDown()
+        verifierForFloating.assertReceivedMove()
+
+        // ACTION_CANCEL with cancel flag (activity window)
+        sendPointersEvent(downTime, ACTION_POINTER_1_UP,
+                arrayOf(pointerInFloating, pointerOutsideFloating),
+                MotionEvent.FLAG_CANCELED, true /*sync*/)
+        verifier.assertReceivedCancel()
+        verifierForFloating.assertReceivedMove()
+
+        // First finger up (floating window)
+        sendEvent(downTime, MotionEvent.ACTION_UP, pointerInFloating, true /*sync*/)
+        verifierForFloating.assertReceivedUp()
+    }
+
+    private fun sendEvent(downTime: Long, action: Int, pt: PointF, sync: Boolean) {
+        val eventTime = when (action) {
+            MotionEvent.ACTION_DOWN -> downTime
+            else -> SystemClock.uptimeMillis()
+        }
+        val event = MotionEvent.obtain(downTime, eventTime, action, pt.x, pt.y, 0 /*metaState*/)
+        event.source = InputDevice.SOURCE_TOUCHSCREEN
+        instrumentation.uiAutomation.injectInputEvent(event, sync)
+    }
+
+    private fun sendPointersEvent(
+        downTime: Long,
+        action: Int,
+        pointers: Array<PointF>,
+        flags: Int,
+        sync: Boolean
+    ) {
+        val eventTime = SystemClock.uptimeMillis()
+        val pointerCount = pointers.size
+        val properties = arrayOfNulls<MotionEvent.PointerProperties>(pointerCount)
+        val coords = arrayOfNulls<MotionEvent.PointerCoords>(pointerCount)
+
+        for (i in 0 until pointerCount) {
+            properties[i] = MotionEvent.PointerProperties()
+            properties[i]!!.id = i
+            properties[i]!!.toolType = MotionEvent.TOOL_TYPE_FINGER
+            coords[i] = MotionEvent.PointerCoords()
+            coords[i]!!.x = pointers[i].x
+            coords[i]!!.y = pointers[i].y
+        }
+
+        val event = MotionEvent.obtain(downTime, eventTime, action, pointerCount,
+                properties, coords, 0 /*metaState*/, 0 /*buttonState*/,
+                0f /*xPrecision*/, 0f /*yPrecision*/, 0 /*deviceId*/, 0 /*edgeFlags*/,
+                InputDevice.SOURCE_TOUCHSCREEN, flags)
+        instrumentation.uiAutomation.injectInputEvent(event, sync)
+    }
+
+    private fun addFloatingWindow(): View {
+        val view = View(instrumentation.targetContext)
+        val layoutParams = WindowManager.LayoutParams(
+                WindowManager.LayoutParams.TYPE_APPLICATION,
+                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+                        or WindowManager.LayoutParams.FLAG_SPLIT_TOUCH)
+        layoutParams.x = 0
+        layoutParams.y = 0
+        layoutParams.width = 100
+        layoutParams.height = 100
+        layoutParams.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
+
+        activity.runOnUiThread {
+            view.setBackgroundColor(android.graphics.Color.RED)
+            activity.windowManager.addView(view, layoutParams)
+        }
+
+        PollingCheck.waitFor {
+            view.hasWindowFocus()
+        }
+        instrumentation.uiAutomation.syncInputTransactions()
+        return view
+    }
+
+    inner class EventVerifier(val getInputEvent: () -> InputEvent?) {
+        fun assertReceivedPointerCancel() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_POINTER_UP, event.actionMasked)
+            assertEquals(MotionEvent.FLAG_CANCELED, event.flags and MotionEvent.FLAG_CANCELED)
+        }
+
+        fun assertReceivedCancel() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_CANCEL, event.actionMasked)
+            assertEquals(MotionEvent.FLAG_CANCELED, event.flags and MotionEvent.FLAG_CANCELED)
+        }
+
+        fun assertReceivedDown() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_DOWN, event.actionMasked)
+        }
+
+        fun assertReceivedPointerDown() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_POINTER_DOWN, event.actionMasked)
+        }
+
+        fun assertReceivedMove() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_MOVE, event.actionMasked)
+        }
+
+        fun assertReceivedUp() {
+            val event = getInputEvent() as MotionEvent
+            assertEquals(MotionEvent.ACTION_UP, event.actionMasked)
+        }
+    }
+
+    companion object {
+        val ACTION_POINTER_1_DOWN = (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) or
+                MotionEvent.ACTION_POINTER_DOWN
+        val ACTION_POINTER_1_UP = (1 shl MotionEvent.ACTION_POINTER_INDEX_SHIFT) or
+                MotionEvent.ACTION_POINTER_UP
+    }
+}
diff --git a/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt b/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt
new file mode 100644
index 0000000..b6f5a3c
--- /dev/null
+++ b/tests/input/src/android/input/cts/VerifyHardwareKeyEventTest.kt
@@ -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.input.cts
+
+import android.hardware.input.InputManager
+import android.view.InputDevice
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.PollingCheck
+import com.android.cts.input.UinputDevice
+import org.junit.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private fun injectEvents(device: UinputDevice, events: IntArray) {
+    device.injectEvents(events.joinToString(prefix = "[", postfix = "]", separator = ","))
+}
+
+/**
+ * Create a virtual keyboard and inject a 'hardware' key event. Ensure that the event can be
+ * verified using the InputManager::verifyInputEvent api.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class VerifyHardwareKeyEventTest {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+
+    @get:Rule
+    val rule = ActivityScenarioRule<CaptureEventActivity>(CaptureEventActivity::class.java)
+
+    private lateinit var activity: CaptureEventActivity
+    private lateinit var inputManager: InputManager
+
+    @Before
+    fun setUp() {
+        rule.getScenario().onActivity {
+            inputManager = it.getSystemService(InputManager::class.java)
+            activity = it
+        }
+        PollingCheck.waitFor { activity.hasWindowFocus() }
+    }
+
+    fun assertReceivedEventsCanBeVerified(numEvents: Int) {
+        for (i in 1..numEvents) {
+            val lastInputEvent = activity.getInputEvent()
+            assertNotNull("Event number $i is null!", lastInputEvent)
+            assertNotNull(inputManager.verifyInputEvent(lastInputEvent!!))
+        }
+    }
+
+    /**
+     * Send a hardware key event and check that InputManager::verifyInputEvent returns non-null
+     * result.
+     */
+    @Test
+    fun testVerifyHardwareKeyEvent() {
+        val keyboardDevice = UinputDevice.create(instrumentation, R.raw.test_keyboard_register,
+                InputDevice.SOURCE_KEYBOARD)
+
+        val EV_SYN = 0
+        val SYN_REPORT = 0
+        val EV_KEY = 1
+        val EV_KEY_DOWN = 1
+        val EV_KEY_UP = 0
+        val KEY_A = 30
+
+        injectEvents(keyboardDevice, intArrayOf(EV_KEY, KEY_A, EV_KEY_DOWN, EV_SYN, SYN_REPORT, 0))
+        // Send the UP event right away to avoid key repeat
+        injectEvents(keyboardDevice, intArrayOf(EV_KEY, KEY_A, EV_KEY_UP, EV_SYN, SYN_REPORT, 0))
+
+        assertReceivedEventsCanBeVerified(2 /*numEvents*/)
+    }
+}
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index ceb6d02..4902be2 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -27,12 +27,15 @@
     compile_multilib: "both",
     libs: ["android.test.runner"],
     static_libs: [
+        "androidx.test.ext.junit",
         "androidx.test.rules",
         "androidx.test.uiautomator_uiautomator",
         "compatibility-device-util-axt",
+        "cts-wm-util",
         "ctstestrunner-axt",
         "CtsMockInputMethodLib",
         "CtsMockSpellCheckerLib",
+        "CtsLegacyImeClientTestLib",
         "testng",
         "kotlin-test",
     ],
diff --git a/tests/inputmethod/legacyimeclienttestlib/Android.bp b/tests/inputmethod/legacyimeclienttestlib/Android.bp
new file mode 100644
index 0000000..59d7782
--- /dev/null
+++ b/tests/inputmethod/legacyimeclienttestlib/Android.bp
@@ -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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_helper_library {
+    name: "CtsLegacyImeClientTestLib",
+
+    // Ideally we want to use SDK version 3, but it's not available.
+    // Note that several new APIs were added into InputConnection in API 9.
+    sdk_version: "4",
+
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "androidx.annotation_annotation",
+    ],
+}
diff --git a/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java b/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java
new file mode 100644
index 0000000..2c4a0da
--- /dev/null
+++ b/tests/inputmethod/legacyimeclienttestlib/src/com/android/cts/inputmethod/LegacyImeClientTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * 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.inputmethod;
+
+import android.os.Bundle;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+
+import androidx.annotation.AnyThread;
+
+/**
+ * A utility class to test compatibility with legacy apps that were built with old SDKs.
+ */
+public final class LegacyImeClientTestUtils {
+
+    /**
+     * Not intented to be instantiated.
+     */
+    private LegacyImeClientTestUtils() {
+    }
+
+    private static final class MinimallyImplementedNoOpInputConnection implements InputConnection {
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public CharSequence getTextBeforeCursor(int n, int flags) {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public CharSequence getTextAfterCursor(int n, int flags) {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public int getCursorCapsMode(int reqModes) {
+            return 0;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean setComposingText(CharSequence text, int newCursorPosition) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean finishComposingText() {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean commitText(CharSequence text, int newCursorPosition) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean commitCompletion(CompletionInfo text) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean setSelection(int start, int end) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean performEditorAction(int editorAction) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean performContextMenuAction(int id) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean beginBatchEdit() {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean endBatchEdit() {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean sendKeyEvent(KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean clearMetaKeyStates(int states) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean reportFullscreenMode(boolean enabled) {
+            return false;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean performPrivateCommand(String action, Bundle data) {
+            return false;
+        }
+    }
+
+    /**
+     * @return an instance of {@link InputConnection} that implements only methods that were
+     * available in {@link android.os.Build.VERSION_CODES#CUPCAKE}.
+     */
+    @AnyThread
+    public static InputConnection createMinimallyImplementedNoOpInputConnection() {
+        return new MinimallyImplementedNoOpInputConnection();
+    }
+}
diff --git a/tests/inputmethod/mockime/res/xml/method.xml b/tests/inputmethod/mockime/res/xml/method.xml
index 48f4a78..6757eeb 100644
--- a/tests/inputmethod/mockime/res/xml/method.xml
+++ b/tests/inputmethod/mockime/res/xml/method.xml
@@ -17,5 +17,6 @@
 
 <input-method xmlns:android="http://schemas.android.com/apk/res/android"
               android:supportsInlineSuggestions="true"
-              android:configChanges="fontScale">
+              android:configChanges="fontScale"
+              android:supportsStylusHandwriting="true">
 </input-method>
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
index abfae95..6c2ced4 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -21,6 +21,7 @@
 import android.os.Handler;
 import android.os.Parcelable;
 import android.view.View;
+import android.view.inputmethod.TextSnapshot;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -63,6 +64,9 @@
         if (object instanceof Handler) {
             return ReturnType.KnownUnsupportedType;
         }
+        if (object instanceof TextSnapshot) {
+            return ReturnType.KnownUnsupportedType;
+        }
         if (object instanceof Boolean) {
             return ReturnType.Boolean;
         }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index bf301d6..cb49460 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -18,6 +18,7 @@
 
 import android.os.SystemClock;
 import android.text.TextUtils;
+import android.util.Pair;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 
@@ -332,6 +333,63 @@
     }
 
     /**
+     * Assert that the {@link MockIme} will not be terminated abruptly with executing a command to
+     * check if it's still alive and verify the number of create/destroy callback should be paired.
+     *
+     * @param session {@link MockImeSession} to be checked.
+     * @param timeout timeout in millisecond to check if {@link MockIme} is still alive.
+     * @throws Exception
+     */
+    public static void expectNoImeCrash(@NonNull MockImeSession session, long timeout)
+            throws Exception {
+        // Issue any trivial command to make sure that the MockIme is still alive.
+        final ImeCommand command = session.callGetDisplayId();
+        expectCommand(session.openEventStream(), command, timeout);
+        // A filter that matches exit events of "onCreate", "onDestroy", and the *command* above.
+        final Predicate<ImeEvent> matcher = event -> {
+            if (!event.isEnterEvent()) {
+                return false;
+            }
+            switch (event.getEventName()) {
+                case "onHandleCommand": {
+                    final ImeCommand eventCommand =
+                            ImeCommand.fromBundle(event.getArguments().getBundle("command"));
+                    return eventCommand.getId() == command.getId();
+                }
+                case "onCreate":
+                case "onDestroy":
+                    return true;
+                default:
+                    return false;
+            }
+        };
+        final ImeEventStream stream = session.openEventStream();
+        String lastEventName = null;
+        // Allowed pairs of (lastEventName, eventName):
+        //  - (null, "onCreate")
+        //  - ("onCreate", "onDestroy")
+        //  - ("onCreate", "onHandleCommand") -> then stop searching
+        //  - ("onDestroy", "onCreate")
+        while (true) {
+            final String eventName =
+                    stream.seekToFirst(matcher).map(ImeEvent::getEventName).orElse("");
+            final Pair<String, String> pair = Pair.create(lastEventName, eventName);
+            if (pair.equals(Pair.create("onCreate", "onHandleCommand"))) {
+                break;  // Done!
+            }
+            if (pair.equals(Pair.create(null, "onCreate"))
+                    || pair.equals(Pair.create("onCreate", "onDestroy"))
+                    || pair.equals(Pair.create("onDestroy", "onCreate"))) {
+                lastEventName = eventName;
+                stream.skip(1);
+                continue;
+            }
+            throw new AssertionError("IME might have crashed. lastEventName="
+                    + lastEventName + " eventName=" + eventName + "\n" + stream.dump());
+        }
+    }
+
+    /**
      * Waits until {@code MockIme} does not send {@code "onInputViewLayoutChanged"} event
      * for a certain period of time ({@code stableThresholdTime} msec).
      *
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 5757750..d2a8743 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -16,13 +16,18 @@
 
 package com.android.cts.mockime;
 
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
 import android.os.Bundle;
 import android.os.PersistableBundle;
 
 import androidx.annotation.ColorInt;
+import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.lang.annotation.Retention;
+
 /**
  * An immutable data store to control the behavior of {@link MockIme}.
  */
@@ -44,7 +49,7 @@
     private static final String DRAWS_BEHIND_NAV_BAR = "drawsBehindNavBar";
     private static final String WINDOW_FLAGS = "WindowFlags";
     private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
-    private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
+    private static final String FULLSCREEN_MODE_POLICY = "FullscreenModePolicy";
     private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
     private static final String WATERMARK_ENABLED = "WatermarkEnabled";
     private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
@@ -58,6 +63,40 @@
     @NonNull
     private final PersistableBundle mBundle;
 
+
+    @Retention(SOURCE)
+    @IntDef(value = {
+            FullscreenModePolicy.NO_FULLSCREEN,
+            FullscreenModePolicy.FORCE_FULLSCREEN,
+            FullscreenModePolicy.OS_DEFAULT,
+    })
+    public @interface FullscreenModePolicy {
+        /**
+         * Let {@link MockIme} always return {@code false} from
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+         *
+         * <p>This is chosen to be the default behavior of {@link MockIme} to make CTS tests most
+         * deterministic.</p>
+         */
+        int NO_FULLSCREEN = 0;
+
+        /**
+         * Let {@link MockIme} always return {@code true} from
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+         *
+         * <p>This can be used to test the behaviors when a full-screen IME is running.</p>
+         */
+        int FORCE_FULLSCREEN = 1;
+
+        /**
+         * Let {@link MockIme} always return the default behavior of
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
+         *
+         * <p>This can be used to test the default behavior of that public API.</p>
+         */
+        int OS_DEFAULT = 2;
+    }
+
     ImeSettings(@NonNull String clientPackageName, @NonNull Bundle bundle) {
         mClientPackageName = clientPackageName;
         mEventCallbackActionName = bundle.getString(EVENT_CALLBACK_INTENT_ACTION_KEY);
@@ -74,8 +113,9 @@
         return mClientPackageName;
     }
 
-    public boolean fullscreenModeAllowed(boolean defaultValue) {
-        return mBundle.getBoolean(FULLSCREEN_MODE_ALLOWED, defaultValue);
+    @FullscreenModePolicy
+    public int fullscreenModePolicy() {
+        return mBundle.getInt(FULLSCREEN_MODE_POLICY);
     }
 
     @ColorInt
@@ -152,15 +192,14 @@
         private final PersistableBundle mBundle = new PersistableBundle();
 
         /**
-         * Controls whether fullscreen mode is allowed or not.
+         * Controls how MockIme reacts to
+         * {@link android.inputmethodservice.InputMethodService#onEvaluateFullscreenMode()}.
          *
-         * <p>By default, fullscreen mode is not allowed in {@link MockIme}.</p>
-         *
-         * @param allowed {@code true} if fullscreen mode is allowed
+         * @param policy one of {@link FullscreenModePolicy}
          * @see MockIme#onEvaluateFullscreenMode()
          */
-        public Builder setFullscreenModeAllowed(boolean allowed) {
-            mBundle.putBoolean(FULLSCREEN_MODE_ALLOWED, allowed);
+        public Builder setFullscreenModePolicy(@FullscreenModePolicy int policy) {
+            mBundle.putInt(FULLSCREEN_MODE_POLICY, policy);
             return this;
         }
 
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 342ab91..2373c79 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -64,6 +64,7 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.TextAttribute;
 import android.widget.FrameLayout;
 import android.widget.HorizontalScrollView;
 import android.widget.ImageView;
@@ -210,6 +211,13 @@
                         final int flag = command.getExtras().getInt("flag");
                         return getMemorizedOrCurrentInputConnection().getSelectedText(flag);
                     }
+                    case "getSurroundingText": {
+                        final int beforeLength = command.getExtras().getInt("beforeLength");
+                        final int afterLength = command.getExtras().getInt("afterLength");
+                        final int flags = command.getExtras().getInt("flags");
+                        return getMemorizedOrCurrentInputConnection().getSurroundingText(
+                                beforeLength, afterLength, flags);
+                    }
                     case "getCursorCapsMode": {
                         final int reqModes = command.getExtras().getInt("reqModes");
                         return getMemorizedOrCurrentInputConnection().getCursorCapsMode(reqModes);
@@ -233,28 +241,54 @@
                         return getMemorizedOrCurrentInputConnection()
                                 .deleteSurroundingTextInCodePoints(beforeLength, afterLength);
                     }
-                    case "setComposingText": {
+                    case "setComposingText(CharSequence,int)": {
                         final CharSequence text = command.getExtras().getCharSequence("text");
                         final int newCursorPosition =
                                 command.getExtras().getInt("newCursorPosition");
                         return getMemorizedOrCurrentInputConnection().setComposingText(
                                 text, newCursorPosition);
                     }
-                    case "setComposingRegion": {
+                    case "setComposingText(CharSequence,int,TextAttribute)": {
+                        final CharSequence text = command.getExtras().getCharSequence("text");
+                        final int newCursorPosition =
+                                command.getExtras().getInt("newCursorPosition");
+                        final TextAttribute textAttribute =
+                                command.getExtras().getParcelable("textAttribute");
+                        return getMemorizedOrCurrentInputConnection()
+                                .setComposingText(text, newCursorPosition, textAttribute);
+                    }
+                    case "setComposingRegion(int,int)": {
                         final int start = command.getExtras().getInt("start");
                         final int end = command.getExtras().getInt("end");
                         return getMemorizedOrCurrentInputConnection().setComposingRegion(start,
                                 end);
                     }
+                    case "setComposingRegion(int,int,TextAttribute)": {
+                        final int start = command.getExtras().getInt("start");
+                        final int end = command.getExtras().getInt("end");
+                        final TextAttribute textAttribute =
+                                command.getExtras().getParcelable("textAttribute");
+                        return getMemorizedOrCurrentInputConnection()
+                                .setComposingRegion(start, end, textAttribute);
+                    }
                     case "finishComposingText":
                         return getMemorizedOrCurrentInputConnection().finishComposingText();
-                    case "commitText": {
+                    case "commitText(CharSequence,int)": {
                         final CharSequence text = command.getExtras().getCharSequence("text");
                         final int newCursorPosition =
                                 command.getExtras().getInt("newCursorPosition");
                         return getMemorizedOrCurrentInputConnection().commitText(text,
                                 newCursorPosition);
                     }
+                    case "commitText(CharSequence,int,TextAttribute)": {
+                        final CharSequence text = command.getExtras().getCharSequence("text");
+                        final int newCursorPosition =
+                                command.getExtras().getInt("newCursorPosition");
+                        final TextAttribute textAttribute =
+                                command.getExtras().getParcelable("textAttribute");
+                        return getMemorizedOrCurrentInputConnection()
+                                .commitText(text, newCursorPosition, textAttribute);
+                    }
                     case "commitCompletion": {
                         final CompletionInfo text = command.getExtras().getParcelable("text");
                         return getMemorizedOrCurrentInputConnection().commitCompletion(text);
@@ -298,6 +332,9 @@
                     case "performSpellCheck": {
                         return getMemorizedOrCurrentInputConnection().performSpellCheck();
                     }
+                    case "takeSnapshot": {
+                        return getMemorizedOrCurrentInputConnection().takeSnapshot();
+                    }
                     case "performPrivateCommand": {
                         final String action = command.getExtras().getString("action");
                         final Bundle data = command.getExtras().getBundle("data");
@@ -322,6 +359,12 @@
                         return getMemorizedOrCurrentInputConnection().commitContent(
                                 inputContentInfo, flags, opts);
                     }
+                    case "setImeConsumesInput": {
+                        final boolean imeConsumesInput =
+                                command.getExtras().getBoolean("imeConsumesInput");
+                        return getMemorizedOrCurrentInputConnection().setImeConsumesInput(
+                                imeConsumesInput);
+                    }
                     case "setBackDisposition": {
                         final int backDisposition =
                                 command.getExtras().getInt("backDisposition");
@@ -424,6 +467,14 @@
 
                         return ImeEvent.RETURN_VALUE_UNAVAILABLE;
                     }
+                    case "getStylusHandwritingWindowVisibility": {
+                        View decorView = getStylusHandwritingWindow().getDecorView();
+                        return decorView != null && decorView.getVisibility() == View.VISIBLE;
+                    }
+                    case "finishStylusHandwriting": {
+                        finishStylusHandwriting();
+                        return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+                    }
                     case "getCurrentWindowMetricsBounds": {
                         return getSystemService(WindowManager.class)
                                 .getCurrentWindowMetrics().getBounds();
@@ -541,7 +592,7 @@
             final Handler handler = new Handler(mHandlerThread.getLooper());
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler,
-                        Context.RECEIVER_VISIBLE_TO_INSTANT_APPS);
+                        Context.RECEIVER_VISIBLE_TO_INSTANT_APPS | Context.RECEIVER_EXPORTED);
             } else {
                 registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
             }
@@ -589,8 +640,20 @@
 
     @Override
     public boolean onEvaluateFullscreenMode() {
-        return getTracer().onEvaluateFullscreenMode(() ->
-                mSettings.fullscreenModeAllowed(false) && super.onEvaluateFullscreenMode());
+        return getTracer().onEvaluateFullscreenMode(() -> {
+            final int policy = mSettings.fullscreenModePolicy();
+            switch (policy) {
+                case ImeSettings.FullscreenModePolicy.NO_FULLSCREEN:
+                    return false;
+                case ImeSettings.FullscreenModePolicy.FORCE_FULLSCREEN:
+                    return true;
+                case ImeSettings.FullscreenModePolicy.OS_DEFAULT:
+                    return super.onEvaluateFullscreenMode();
+                default:
+                    Log.e(TAG, "unknown FullscreenModePolicy=" + policy);
+                    return false;
+            }
+        });
     }
 
     private static final class KeyboardLayoutView extends LinearLayout {
@@ -794,6 +857,18 @@
     }
 
     @Override
+    public boolean onStartStylusHandwriting() {
+        getTracer().onStartStylusHandwriting(() -> super.onStartStylusHandwriting());
+        return true;
+    }
+
+    @Override
+    public void onFinishStylusHandwriting() {
+        getTracer().onFinishStylusHandwriting(() -> super.onFinishStylusHandwriting());
+    }
+
+
+    @Override
     public void onFinishInputView(boolean finishingInput) {
         getTracer().onFinishInputView(finishingInput,
                 () -> super.onFinishInputView(finishingInput));
@@ -917,8 +992,25 @@
         stylesBuilder.addStyle(InlineSuggestionUi.newStyleBuilder().build());
         Bundle styles = stylesBuilder.build();
 
+        final boolean supportedClientInlineSuggestions;
+        final boolean supportedServiceInlineSuggestions;
+        final boolean supportedInlineSuggestions;
         if (mInlineSuggestionsExtras != null) {
             styles.putAll(mInlineSuggestionsExtras);
+            supportedClientInlineSuggestions =
+                    mInlineSuggestionsExtras.getBoolean("ClientSuggestions", true);
+            supportedServiceInlineSuggestions =
+                    mInlineSuggestionsExtras.getBoolean("ServiceSuggestions", true);
+            supportedInlineSuggestions =
+                    mInlineSuggestionsExtras.getBoolean("InlineSuggestions", true);
+        } else {
+            supportedClientInlineSuggestions = true;
+            supportedServiceInlineSuggestions = true;
+            supportedInlineSuggestions = true;
+        }
+
+        if (!supportedInlineSuggestions) {
+            return null;
         }
 
         return getTracer().onCreateInlineSuggestionsRequest(() -> {
@@ -934,6 +1026,8 @@
             final InlineSuggestionsRequest.Builder builder =
                     new InlineSuggestionsRequest.Builder(presentationSpecs)
                             .setInlineTooltipPresentationSpec(tooltipSpec)
+                            .setClientSupported(supportedClientInlineSuggestions)
+                            .setServiceSupported(supportedServiceInlineSuggestions)
                             .setMaxSuggestionCount(6);
             if (mInlineSuggestionsExtras != null) {
                 builder.setExtras(mInlineSuggestionsExtras.deepCopy());
@@ -1141,6 +1235,14 @@
             recordEventInternal("onStartInputView", runnable, arguments);
         }
 
+        void onStartStylusHandwriting(@NonNull Runnable runnable) {
+            recordEventInternal("onStartStylusHandwriting", runnable);
+        }
+
+        void onFinishStylusHandwriting(@NonNull Runnable runnable) {
+            recordEventInternal("onFinishStylusHandwriting", runnable);
+        }
+
         void onFinishInputView(boolean finishingInput, @NonNull Runnable runnable) {
             final Bundle arguments = new Bundle();
             arguments.putBoolean("finishingInput", finishingInput);
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 2aa20c1..93c04747 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -30,6 +30,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -45,8 +46,11 @@
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextAttribute;
 
+import androidx.annotation.AnyThread;
 import androidx.annotation.GuardedBy;
+import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
@@ -76,6 +80,9 @@
     @NonNull
     private final UiAutomation mUiAutomation;
 
+    @NonNull
+    private final AtomicBoolean mActive = new AtomicBoolean(true);
+
     private final HandlerThread mHandlerThread = new HandlerThread("EventReceiver");
 
     private static final class EventStore {
@@ -211,9 +218,15 @@
         writeMockImeSettings(mContext, mImeEventActionName, imeSettings);
 
         mHandlerThread.start();
-        mContext.registerReceiver(mEventReceiver,
-                new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
-                new Handler(mHandlerThread.getLooper()));
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+            mContext.registerReceiver(mEventReceiver,
+                    new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+                    new Handler(mHandlerThread.getLooper()), Context.RECEIVER_EXPORTED);
+        } else {
+            mContext.registerReceiver(mEventReceiver,
+                    new IntentFilter(mImeEventActionName), null /* broadcastPermission */,
+                    new Handler(mHandlerThread.getLooper()));
+        }
 
         executeShellCommand(mUiAutomation, "ime enable " + getMockImeId());
         executeShellCommand(mUiAutomation, "ime set " + getMockImeId());
@@ -292,10 +305,20 @@
     }
 
     /**
+     * @return {@code true} until {@link #close()} gets called.
+     */
+    @AnyThread
+    public boolean isActive() {
+        return mActive.get();
+    }
+
+    /**
      * Closes the active session and de-selects {@link MockIme}. Currently which IME will be
      * selected next is up to the system.
      */
     public void close() throws Exception {
+        mActive.set(false);
+
         executeShellCommand(mUiAutomation, "ime reset");
 
         PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
@@ -440,6 +463,36 @@
     }
 
     /**
+     * Lets {@link MockIme} to call {@link InputConnection#getSurroundingText(int, int, int)} with
+     * the given parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().getSurroundingText(int, int, int)}.</p>
+     *
+     * <p>Use {@link ImeEvent#getReturnParcelableValue()} for {@link ImeEvent} returned from
+     * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+     * value returned from the API.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param beforeLength The expected length of the text before the cursor.
+     * @param afterLength The expected length of the text after the cursor.
+     * @param flags Supplies additional options controlling how the text is returned. May be either
+     *              {@code 0} or {@link InputConnection#GET_TEXT_WITH_STYLES}.
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}.
+     */
+    @NonNull
+    public ImeCommand callGetSurroundingText(@IntRange(from = 0) int beforeLength,
+            @IntRange(from = 0) int afterLength, int flags) {
+        final Bundle params = new Bundle();
+        params.putInt("beforeLength", beforeLength);
+        params.putInt("afterLength", afterLength);
+        params.putInt("flags", flags);
+        return callCommandInternal("getSurroundingText", params);
+    }
+
+    /**
      * Lets {@link MockIme} to call {@link InputConnection#getCursorCapsMode(int)} with the given
      * parameters.
      *
@@ -569,7 +622,39 @@
         final Bundle params = new Bundle();
         params.putCharSequence("text", text);
         params.putInt("newCursorPosition", newCursorPosition);
-        return callCommandInternal("setComposingText", params);
+        return callCommandInternal("setComposingText(CharSequence,int)", params);
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} with the given
+     * parameters.
+     *
+     * <p>This triggers
+     * {@code getCurrentInputConnection().setComposingText(text, newCursorPosition, textAttribute)}.
+     * </p>
+     *
+     * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+     * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+     * value returned from the API.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param text to be passed as the {@code text} parameter
+     * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+     * @param textAttribute to be passed as the {@code textAttribute} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callSetComposingText(@Nullable CharSequence text, int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        final Bundle params = new Bundle();
+        params.putCharSequence("text", text);
+        params.putInt("newCursorPosition", newCursorPosition);
+        params.putParcelable("textAttribute", textAttribute);
+        return callCommandInternal("setComposingText(CharSequence,int,TextAttribute)", params);
     }
 
     /**
@@ -595,7 +680,37 @@
         final Bundle params = new Bundle();
         params.putInt("start", start);
         params.putInt("end", end);
-        return callCommandInternal("setComposingRegion", params);
+        return callCommandInternal("setComposingRegion(int,int)", params);
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link InputConnection#setComposingRegion(int, int, TextAttribute)} with the given
+     * parameters.
+     *
+     * <p>This triggers
+     * {@code getCurrentInputConnection().setComposingRegion(start, end, TextAttribute)}.</p>
+     *
+     * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+     * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+     * value returned from the API.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param start to be passed as the {@code start} parameter
+     * @param end to be passed as the {@code end} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}.
+     */
+    @NonNull
+    public ImeCommand callSetComposingRegion(int start, int end,
+            @Nullable TextAttribute textAttribute) {
+        final Bundle params = new Bundle();
+        params.putInt("start", start);
+        params.putInt("end", end);
+        params.putParcelable("textAttribute", textAttribute);
+        return callCommandInternal("setComposingRegion(int,int,TextAttribute)", params);
     }
 
     /**
@@ -643,7 +758,37 @@
         final Bundle params = new Bundle();
         params.putCharSequence("text", text);
         params.putInt("newCursorPosition", newCursorPosition);
-        return callCommandInternal("commitText", params);
+        return callCommandInternal("commitText(CharSequence,int)", params);
+    }
+
+    /**
+     * Lets {@link MockIme} to call
+     * {@link InputConnection#commitText(CharSequence, int, TextAttribute)} with the given
+     * parameters.
+     *
+     * <p>This triggers
+     * {@code getCurrentInputConnection().commitText(text, newCursorPosition, TextAttribute)}.</p>
+     *
+     * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+     * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+     * value returned from the API.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param text to be passed as the {@code text} parameter
+     * @param newCursorPosition to be passed as the {@code newCursorPosition} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callCommitText(@Nullable CharSequence text, int newCursorPosition,
+            @Nullable TextAttribute textAttribute) {
+        final Bundle params = new Bundle();
+        params.putCharSequence("text", text);
+        params.putInt("newCursorPosition", newCursorPosition);
+        params.putParcelable("textAttribute", textAttribute);
+        return callCommandInternal("commitText(CharSequence,int,TextAttribute)", params);
     }
 
     /**
@@ -853,6 +998,22 @@
     }
 
     /**
+     * Lets {@link MockIme} to call {@link InputConnection#takeSnapshot()}.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().takeSnapshot()}.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callTakeSnapshot() {
+        return callCommandInternal("takeSnapshot", new Bundle());
+    }
+
+    /**
      * Lets {@link MockIme} to call {@link InputConnection#clearMetaKeyStates(int)} with the given
      * parameters.
      *
@@ -1024,6 +1185,30 @@
     }
 
     /**
+     * Lets {@link MockIme} to call {@link InputConnection#setImeConsumesInput(boolean)} with the
+     * given parameters.
+     *
+     * <p>This triggers {@code getCurrentInputConnection().setImeConsumesInput(boolean)}.</p>
+     *
+     * <p>Use {@link ImeEvent#getReturnBooleanValue()} for {@link ImeEvent} returned from
+     * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to see the
+     * value returned from the API.</p>
+     *
+     * <p>This can be affected by {@link #memorizeCurrentInputConnection()}.</p>
+     *
+     * @param imeConsumesInput to be passed as the {@code imeConsumesInput} parameter
+     * @return {@link ImeCommand} object that can be passed to
+     *         {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+     *         wait until this event is handled by {@link MockIme}
+     */
+    @NonNull
+    public ImeCommand callSetImeConsumesInput(boolean imeConsumesInput) {
+        final Bundle params = new Bundle();
+        params.putBoolean("imeConsumesInput", imeConsumesInput);
+        return callCommandInternal("setImeConsumesInput", params);
+    }
+
+    /**
      * Lets {@link MockIme} to call
      * {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
      * parameters.
@@ -1191,6 +1376,16 @@
     }
 
     @NonNull
+    public ImeCommand callGetStylusHandwritingWindowVisibility() {
+        return callCommandInternal("getStylusHandwritingWindowVisibility", new Bundle());
+    }
+
+    @NonNull
+    public ImeCommand callFinishStylusHandwriting() {
+        return callCommandInternal("finishStylusHandwriting", new Bundle());
+    }
+
+    @NonNull
     public ImeCommand callGetCurrentWindowMetricsBounds() {
         return callCommandInternal("getCurrentWindowMetricsBounds", new Bundle());
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
index f3bd570..8833f41 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -16,6 +16,8 @@
 
 package android.view.inputmethod.cts;
 
+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;
@@ -40,8 +42,10 @@
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextSnapshot;
 import android.view.inputmethod.cts.util.InputConnectionTestUtils;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -53,6 +57,9 @@
 @RunWith(AndroidJUnit4.class)
 public class BaseInputConnectionTest {
 
+    private static final int CAPS_MODE_MASK = TextUtils.CAP_MODE_CHARACTERS
+            | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES;
+
     private static BaseInputConnection createBaseInputConnection() {
         final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
         return new BaseInputConnection(view, true);
@@ -189,6 +196,23 @@
     }
 
     /**
+     * An utility method to create an instance of {@link BaseInputConnection} from {@link Editable}.
+     *
+     * @param editable the initial text.
+     * @return {@link BaseInputConnection} instantiated in the full editor mode with
+     *         {@code editable}.
+     */
+    private static BaseInputConnection createConnection(@NonNull Editable editable) {
+        final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
+        return new BaseInputConnection(view, true) {
+            @Override
+            public Editable getEditable() {
+                return editable;
+            }
+        };
+    }
+
+    /**
      * An utility method to create an instance of {@link BaseInputConnection} in the full editor
      * mode with an initial text and selection range.
      *
@@ -201,13 +225,7 @@
         final int selectionEnd = Selection.getSelectionEnd(source);
         final Editable editable = Editable.Factory.getInstance().newEditable(source);
         Selection.setSelection(editable, selectionStart, selectionEnd);
-        final View view = new View(InstrumentationRegistry.getInstrumentation().getTargetContext());
-        return new BaseInputConnection(view, true) {
-            @Override
-            public Editable getEditable() {
-                return editable;
-            }
-        };
+        return createConnection(editable);
     }
 
     private static void verifyDeleteSurroundingTextMain(final String initialState,
@@ -616,4 +634,44 @@
                 100, BaseInputConnection.GET_TEXT_WITH_STYLES).toString());
         expectThrows(IllegalArgumentException.class, ()-> connection.getTextAfterCursor(-1, 0));
     }
+
+    @Test
+    public void testTakeSnapshot() {
+        final BaseInputConnection connection = createConnectionWithSelection(
+                InputConnectionTestUtils.formatString("0123[456]789"));
+
+        verifyTextSnapshot(connection);
+
+        connection.setSelection(10, 10);
+        verifyTextSnapshot(connection);
+
+        connection.setComposingRegion(3, 10);
+        verifyTextSnapshot(connection);
+
+        connection.finishComposingText();
+        verifyTextSnapshot(connection);
+    }
+
+    @Test
+    public void testTakeSnapshotForNoSelection() {
+        final BaseInputConnection connection = createConnection(
+                Editable.Factory.getInstance().newEditable("test"));
+        // null should be returned for text with no selection.
+        assertThat(connection.takeSnapshot()).isNull();
+    }
+
+    private void verifyTextSnapshot(@NonNull BaseInputConnection connection) {
+        final Editable editable = connection.getEditable();
+
+        final TextSnapshot snapshot = connection.takeSnapshot();
+        assertThat(snapshot).isNotNull();
+        assertThat(snapshot.getSelectionStart()).isEqualTo(Selection.getSelectionStart(editable));
+        assertThat(snapshot.getSelectionEnd()).isEqualTo(Selection.getSelectionEnd(editable));
+        assertThat(snapshot.getCompositionStart())
+                .isEqualTo(BaseInputConnection.getComposingSpanStart(editable));
+        assertThat(snapshot.getCompositionEnd())
+                .isEqualTo(BaseInputConnection.getComposingSpanEnd(editable));
+        assertThat(snapshot.getCursorCapsMode()).isEqualTo(
+                connection.getCursorCapsMode(CAPS_MODE_MASK));
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
index 466a7cd..d8128a8 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/CursorAnchorInfoTest.java
@@ -32,6 +32,7 @@
 import android.text.TextUtils;
 import android.view.inputmethod.CursorAnchorInfo;
 import android.view.inputmethod.CursorAnchorInfo.Builder;
+import android.view.inputmethod.EditorBoundsInfo;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -103,12 +104,17 @@
         Matrix transformMatrix = new Matrix();
         transformMatrix.setScale(10.0f, 20.0f);
 
+        final EditorBoundsInfo boundsInfo =
+                new EditorBoundsInfo.Builder().setEditorBounds(MANY_BOUNDS[0])
+                        .setHandwritingBounds(MANY_BOUNDS[1]).build();
+
         final Builder builder = new Builder();
         builder.setSelectionRange(selectionStart, selectionEnd)
                 .setComposingText(composingTextStart, composingText)
                 .setInsertionMarkerLocation(insertionMarkerHorizontal, insertionMarkerTop,
                         insertionMarkerBaseline, insertionMarkerBottom, insertionMarkerFlags)
-                .setMatrix(transformMatrix);
+                .setMatrix(transformMatrix)
+                .setEditorBoundsInfo(boundsInfo);
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF bounds = MANY_BOUNDS[i];
             final int flags = MANY_FLAGS_ARRAY[i];
@@ -127,6 +133,11 @@
         assertEquals(insertionMarkerBaseline, info.getInsertionMarkerBaseline(), EPSILON);
         assertEquals(insertionMarkerBottom, info.getInsertionMarkerBottom(), EPSILON);
         assertEquals(transformMatrix, info.getMatrix());
+        assertEquals(boundsInfo, info.getEditorBoundsInfo());
+        assertEquals(MANY_BOUNDS[0],
+                info.getEditorBoundsInfo().getEditorBounds());
+        assertEquals(MANY_BOUNDS[1],
+                info.getEditorBoundsInfo().getHandwritingBounds());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
             assertEquals(expectedBounds, info.getCharacterBounds(i));
@@ -151,6 +162,7 @@
         assertEquals(insertionMarkerTop, info2.getInsertionMarkerTop(), EPSILON);
         assertEquals(insertionMarkerBaseline, info2.getInsertionMarkerBaseline(), EPSILON);
         assertEquals(insertionMarkerBottom, info2.getInsertionMarkerBottom(), EPSILON);
+        assertEquals(boundsInfo, info2.getEditorBoundsInfo());
         assertEquals(transformMatrix, info2.getMatrix());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
@@ -178,6 +190,7 @@
         assertEquals(insertionMarkerTop, info3.getInsertionMarkerTop(), EPSILON);
         assertEquals(insertionMarkerBaseline, info3.getInsertionMarkerBaseline(), EPSILON);
         assertEquals(insertionMarkerBottom, info3.getInsertionMarkerBottom(), EPSILON);
+        assertEquals(boundsInfo, info3.getEditorBoundsInfo());
         assertEquals(transformMatrix, info3.getMatrix());
         for (int i = 0; i < MANY_BOUNDS.length; i++) {
             final RectF expectedBounds = MANY_BOUNDS[i];
@@ -204,6 +217,7 @@
         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerTop(), EPSILON);
         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBaseline(), EPSILON);
         assertEquals(Float.NaN, uninitializedInfo.getInsertionMarkerBottom(), EPSILON);
+        assertEquals(null, uninitializedInfo.getEditorBoundsInfo());
         assertEquals(new Matrix(), uninitializedInfo.getMatrix());
     }
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
index b1e0c1d..66c27f3 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditTextImeSupportTest.java
@@ -30,7 +30,10 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.graphics.Color;
 import android.os.SystemClock;
+import android.view.View;
+import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.SurroundingText;
@@ -198,6 +201,109 @@
     }
 
     /**
+     * Test when to see {@link EditorInfo#IME_FLAG_NAVIGATE_NEXT} and
+     * {@link EditorInfo#IME_FLAG_NAVIGATE_PREVIOUS}.
+     *
+     * <p>This is also a regression test for Bug 31099943.</p>
+     */
+    @Test
+    public void testNavigateFlags() throws Exception {
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            // For a single EditText, there should be no navigate flag
+            verifyNavigateFlags(stream, new Control[]{
+                    Control.FOCUSED_EDIT_TEXT,
+            }, false /* navigateNext */, false /* navigatePrevious */);
+
+            // For two EditText controls, there should be one navigate flag depending on the
+            // geometry.
+            verifyNavigateFlags(stream, new Control[]{
+                    Control.FOCUSED_EDIT_TEXT,
+                    Control.EDIT_TEXT,
+            }, true /* navigateNext */, false /* navigatePrevious */);
+            verifyNavigateFlags(stream, new Control[]{
+                    Control.EDIT_TEXT,
+                    Control.FOCUSED_EDIT_TEXT,
+            }, false /* navigateNext */, true /* navigatePrevious */);
+
+            // Non focusable View controls should be ignored when determining navigation flags.
+            verifyNavigateFlags(stream, new Control[]{
+                    Control.NON_FOCUSABLE_VIEW,
+                    Control.FOCUSED_EDIT_TEXT,
+                    Control.NON_FOCUSABLE_VIEW,
+            }, false /* navigateNext */, false /* navigatePrevious */);
+
+            // Even focusable View controls should be ignored when determining navigation flags if
+            // View#onCheckIsTextEditor() returns false. (Regression test for Bug 31099943)
+            verifyNavigateFlags(stream, new Control[]{
+                    Control.FOCUSABLE_VIEW,
+                    Control.FOCUSED_EDIT_TEXT,
+                    Control.FOCUSABLE_VIEW,
+            }, false /* navigateNext */, false /* navigatePrevious */);
+        }
+    }
+
+    private enum Control {
+        EDIT_TEXT,
+        FOCUSED_EDIT_TEXT,
+        FOCUSABLE_VIEW,
+        NON_FOCUSABLE_VIEW,
+    }
+
+    private void verifyNavigateFlags(@NonNull ImeEventStream stream, @NonNull Control[] controls,
+            boolean navigateNext, boolean navigatePrevious) throws Exception {
+        final String marker = getTestMarker();
+        TestActivity.startSync(activity-> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+            for (Control control : controls) {
+                switch (control) {
+                    case EDIT_TEXT:
+                    case FOCUSED_EDIT_TEXT: {
+                        final boolean focused = (Control.FOCUSED_EDIT_TEXT == control);
+                        final EditText editText = new EditText(activity);
+                        editText.setHint("editText");
+                        layout.addView(editText);
+                        if (focused) {
+                            editText.setPrivateImeOptions(marker);
+                            editText.requestFocus();
+                        }
+                        break;
+                    }
+                    case FOCUSABLE_VIEW:
+                    case NON_FOCUSABLE_VIEW: {
+                        final boolean focusable = (Control.FOCUSABLE_VIEW == control);
+                        final View view = new View(activity);
+                        view.setBackgroundColor(focusable ? Color.YELLOW : Color.RED);
+                        view.setFocusable(focusable);
+                        view.setFocusableInTouchMode(focusable);
+                        view.setLayoutParams(new ViewGroup.LayoutParams(
+                                ViewGroup.LayoutParams.MATCH_PARENT, 10 /* height */));
+                        layout.addView(view);
+                        break;
+                    }
+                    default:
+                        throw new UnsupportedOperationException("Unknown control=" + control);
+                }
+            }
+            return layout;
+        });
+
+        final ImeEvent startInput = expectEvent(stream,
+                editorMatcher("onStartInput", marker), TIMEOUT);
+        final EditorInfo editorInfo = startInput.getArguments().getParcelable("editorInfo");
+        assertThat(editorInfo).isNotNull();
+        assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_NEXT)
+                .isEqualTo(navigateNext ? EditorInfo.IME_FLAG_NAVIGATE_NEXT : 0);
+        assertThat(editorInfo.imeOptions & EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS)
+                .isEqualTo(navigatePrevious ? EditorInfo.IME_FLAG_NAVIGATE_PREVIOUS : 0);
+    }
+
+    /**
      * Regression test for Bug 209958658.
      */
     @Test
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 34d4443..766113f 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -17,7 +17,11 @@
 package android.view.inputmethod.cts;
 
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
@@ -32,6 +36,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -54,6 +59,7 @@
 import android.view.inputmethod.cts.util.AutoCloseableWrapper;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
 import android.view.inputmethod.cts.util.TestActivity;
+import android.view.inputmethod.cts.util.TestActivity2;
 import android.view.inputmethod.cts.util.TestUtils;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
 import android.view.inputmethod.cts.util.WindowFocusHandleService;
@@ -76,6 +82,7 @@
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -136,10 +143,7 @@
     @FlakyTest(bugId = 149246840)
     @Test
     public void testOnStartInputCalledOnceIme() throws Exception {
-        try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
@@ -167,10 +171,7 @@
 
     @Test
     public void testSoftInputStateAlwaysVisibleWithoutFocusedEditorView() throws Exception {
-        try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
@@ -209,11 +210,69 @@
     }
 
     @Test
+    public void testNoEditorNoStartInput() throws Exception {
+        Assume.assumeTrue(isPreventImeStartup());
+        try (MockImeSession imeSession = createTestImeSession()) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final TextView textView = new TextView(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return false;
+                    }
+                };
+                textView.setText("textView");
+                textView.requestFocus();
+                textView.setPrivateImeOptions(marker);
+                layout.addView(textView);
+                return layout;
+            });
+
+            // Input shouldn't start
+            notExpectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+        }
+    }
+
+    @Test
+    public void testDelayedAddEditorStartsInput() throws Exception {
+        Assume.assumeTrue(isPreventImeStartup());
+        try (MockImeSession imeSession = createTestImeSession()) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final AtomicReference<LinearLayout> layoutRef = new AtomicReference<>();
+            final TestActivity testActivity = TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+                layoutRef.set(layout);
+
+                return layout;
+            });
+
+            // Activity adds EditText at a later point.
+            TestUtils.waitOnMainUntil(() -> layoutRef.get().hasWindowFocus(), TIMEOUT);
+            InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+            final String marker = getTestMarker();
+            testActivity.runOnUiThread(() -> {
+                final EditText editText = new EditText(testActivity);
+                editText.setText("Editable");
+                editText.setPrivateImeOptions(marker);
+                layoutRef.get().addView(editText);
+                editText.requestFocus();
+            });
+
+            // Input should start
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+        }
+    }
+
+    @Test
     public void testEditorStartsInput() throws Exception {
-        try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
@@ -236,10 +295,7 @@
 
     @Test
     public void testSoftInputStateAlwaysVisibleFocusedEditorView() throws Exception {
-        try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             TestActivity.startSync(activity -> {
@@ -277,15 +333,12 @@
     @Test
     public void testFocusableWindowDoesNotInvalidateExistingInputConnection() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        try (MockImeSession imeSession = MockImeSession.create(
-                instrumentation.getContext(),
-                instrumentation.getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
             final EditText editText = launchTestActivity(marker);
-            instrumentation.runOnMainSync(() -> editText.requestFocus());
+            instrumentation.runOnMainSync(editText::requestFocus);
 
             // Wait until the MockIme gets bound to the TestActivity.
             expectBindInput(stream, Process.myPid(), TIMEOUT);
@@ -359,7 +412,7 @@
             instrumentation.waitForIdleSync();
 
             // Make sure that the EditText now has window-focus again.
-            TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+            TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
 
             // Make sure that InputConnection#commitText() works.
             final ImeCommand commit4 = imeSession.callCommitText("Done!", 1);
@@ -379,10 +432,7 @@
     @Test
     public void testNonFocusablePopupWindowDoesNotAffectImeVisibility() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        try (MockImeSession imeSession = MockImeSession.create(
-                instrumentation.getContext(),
-                instrumentation.getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
@@ -440,15 +490,12 @@
     @Test
     public void testRestartInputWhileOtherProcessHasWindowFocus() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        try (MockImeSession imeSession = MockImeSession.create(
-                instrumentation.getContext(),
-                instrumentation.getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
             final EditText editText = launchTestActivity(marker);
-            instrumentation.runOnMainSync(() -> editText.requestFocus());
+            instrumentation.runOnMainSync(editText::requestFocus);
 
             // Wait until the MockIme gets bound to the TestActivity.
             expectBindInput(stream, Process.myPid(), TIMEOUT);
@@ -457,7 +504,7 @@
 
             // Get app window token
             final IBinder appWindowToken = TestUtils.getOnMainSync(
-                    () -> editText.getApplicationWindowToken());
+                    editText::getApplicationWindowToken);
 
             try (WindowFocusStealer focusStealer =
                          WindowFocusStealer.connect(instrumentation.getTargetContext(), TIMEOUT)) {
@@ -476,7 +523,7 @@
             }
 
             // Wait until the edit text gains window focus again.
-            TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+            TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
 
             // Make sure that InputConnection#commitText() still works.
             final ImeCommand command = imeSession.callCommitText("test commit", 1);
@@ -492,10 +539,7 @@
      */
     @Test
     public void testSetShowInputOnFocus() throws Exception {
-        try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             final String marker = getTestMarker();
@@ -520,9 +564,7 @@
     public void testMultiWindowFocusHandleOnDifferentUiThread() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         try (CloseOnce session = CloseOnce.of(new ServiceSession(instrumentation.getContext()));
-             MockImeSession imeSession = MockImeSession.create(
-                     instrumentation.getContext(), instrumentation.getUiAutomation(),
-                     new ImeSettings.Builder())) {
+             MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
             final AtomicBoolean popupTextHasWindowFocus = new AtomicBoolean(false);
             final AtomicBoolean popupTextHasViewFocus = new AtomicBoolean(false);
@@ -537,7 +579,7 @@
 
             // Emulate tap event
             CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, editText);
-            TestUtils.waitOnMainUntil(() -> editTextHasWindowFocus.get(), TIMEOUT);
+            TestUtils.waitOnMainUntil(editTextHasWindowFocus::get, TIMEOUT);
 
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
@@ -546,8 +588,8 @@
             final ServiceSession serviceSession = (ServiceSession) session.mAutoCloseable;
             final EditText popupTextView = serviceSession.getService().getPopupTextView(
                     popupTextHasWindowFocus);
-            assertTrue(popupTextView.getHandler().getLooper()
-                    != serviceSession.getService().getMainLooper());
+            assertNotSame(popupTextView.getHandler().getLooper(),
+                    serviceSession.getService().getMainLooper());
 
             // Verify popupTextView will also receive window focus change and soft keyboard shown
             // after tapping the view.
@@ -556,7 +598,7 @@
                 popupTextView.setPrivateImeOptions(marker1);
                 popupTextHasViewFocus.set(popupTextView.requestFocus());
             });
-            TestUtils.waitOnMainUntil(() -> popupTextHasViewFocus.get(), TIMEOUT);
+            TestUtils.waitOnMainUntil(popupTextHasViewFocus::get, TIMEOUT);
 
             CtsTouchUtils.emulateTapOnViewCenter(instrumentation, null, popupTextView);
             TestUtils.waitOnMainUntil(() -> popupTextHasWindowFocus.get()
@@ -575,7 +617,7 @@
             // Remove the popTextView window and back to test activity, and then verify if
             // commitText is still workable.
             session.close();
-            TestUtils.waitOnMainUntil(() -> editText.hasWindowFocus(), TIMEOUT);
+            TestUtils.waitOnMainUntil(editText::hasWindowFocus, TIMEOUT);
             final ImeCommand commit = imeSession.callCommitText("test commit", 1);
             expectCommand(stream, commit, TIMEOUT);
             TestUtils.waitOnMainUntil(
@@ -586,9 +628,7 @@
     @Test
     public void testKeyboardStateAfterImeFocusableFlagChanged() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        try (MockImeSession imeSession = MockImeSession.create(
-                     instrumentation.getContext(), instrumentation.getUiAutomation(),
-                     new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
             final String marker = getTestMarker();
@@ -666,10 +706,7 @@
     @Test
     public void testRequestFocusOnWindowFocusChanged() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        try (MockImeSession imeSession = MockImeSession.create(
-                instrumentation.getContext(),
-                instrumentation.getUiAutomation(),
-                new ImeSettings.Builder())) {
+        try (MockImeSession imeSession = createTestImeSession()) {
             final ImeEventStream stream = imeSession.openEventStream();
             final String marker = getTestMarker();
             final AtomicReference<EditText> editTextRef = new AtomicReference<>();
@@ -711,6 +748,132 @@
         }
     }
 
+    /**
+     * Start an activity with a focused test editor and wait for the IME to become visible,
+     * then start another activity with the given {@code softInputMode} and an <b>unfocused</b>
+     * test editor.
+     *
+     * @return the event stream positioned before the second app is launched
+     */
+    private ImeEventStream startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+            int softInputMode)
+            throws Exception {
+        try (MockImeSession imeSession = createTestImeSession()) {
+            final String marker = getTestMarker();
+
+            // Launch an activity with a text edit and request focus
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setText("editText");
+                editText.setPrivateImeOptions(marker);
+                editText.requestFocus();
+
+                activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                layout.addView(editText);
+                return layout;
+            });
+
+            ImeEventStream stream = imeSession.openEventStream();
+
+            // Wait until the MockIme gets bound and started for the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // Skip events relating to showStateInitializeActivity() and TestActivity1
+            stream.skipAll();
+
+            // Launch another activity without a text edit but with the requested softInputMode set
+            TestActivity2.startSync(activity -> {
+                activity.getWindow().setSoftInputMode(softInputMode);
+
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                // Do not request focus for the editText
+                editText.setText("Unfocused editText");
+                layout.addView(editText);
+                return layout;
+            });
+
+            return stream;
+        }
+    }
+
+    @Test
+    public void testUnfocusedEditor_stateUnspecified_hidesIme() throws Exception {
+        ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+                SOFT_INPUT_STATE_UNSPECIFIED);
+        expectImeHidden(stream);
+        expectOnFinishInput(stream);
+    }
+
+    @Test
+    public void testUnfocusedEditor_stateHidden_hidesIme() throws Exception {
+        ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+                SOFT_INPUT_STATE_HIDDEN);
+        expectImeHidden(stream);
+        expectOnFinishInput(stream);
+    }
+
+    @Test
+    public void testUnfocusedEditor_stateAlwaysHidden_hidesIme() throws Exception {
+        ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+                SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+        expectImeHidden(stream);
+        expectOnFinishInput(stream);
+    }
+
+    @Test
+    public void testUnfocusedEditor_stateVisible_startsIme() throws Exception {
+        ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+                SOFT_INPUT_STATE_VISIBLE);
+        // The previous IME should be finished
+        expectOnFinishInput(stream);
+
+        // Input should be started and shown
+        expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+                EXPECT_TIMEOUT);
+        expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                EXPECT_TIMEOUT);
+    }
+
+    @Test
+    public void testUnfocusedEditor_stateAlwaysVisible_startsIme() throws Exception {
+        ImeEventStream stream = startFocusedEditorActivity_thenAnotherUnfocusedEditorActivity(
+                SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        // The previous IME should be finished
+        expectOnFinishInput(stream);
+
+        // Input should be started and shown
+        expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+                EXPECT_TIMEOUT);
+        expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+                EXPECT_TIMEOUT);
+    }
+
+    private static void expectImeHidden(@NonNull ImeEventStream stream) throws TimeoutException {
+        expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), EXPECT_TIMEOUT);
+    }
+
+    private static void expectOnFinishInput(@NonNull ImeEventStream stream)
+            throws TimeoutException {
+        expectEvent(stream, event -> "onFinishInput".equals(event.getEventName()), EXPECT_TIMEOUT);
+    }
+
+    @NonNull
+    private static MockImeSession createTestImeSession() throws Exception {
+        Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        return MockImeSession.create(
+                instrumentation.getContext(),
+                instrumentation.getUiAutomation(),
+                new ImeSettings.Builder());
+    }
+
     private static class ServiceSession implements ServiceConnection, AutoCloseable {
         private final Context mContext;
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
index 1eac684..34de0c0 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsVisibilityTest.java
@@ -22,8 +22,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 
@@ -56,6 +54,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -122,26 +121,19 @@
                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
             expectImeVisible(TIMEOUT);
 
-            final View[] childViewRoot = new View[1];
-            TestUtils.runOnMainSync(() -> {
-                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-                attrs.token = activity.getWindow().getAttributes().token;
-                attrs.type = TYPE_APPLICATION;
-                attrs.width = 200;
-                attrs.height = 200;
-                attrs.format = PixelFormat.TRANSPARENT;
-                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
-                attrs.setFitInsetsTypes(WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
-                        | WindowInsets.Type.navigationBars());
-                childViewRoot[0] = addChildWindow(activity, attrs);
-                childViewRoot[0].setVisibility(View.VISIBLE);
-            });
-            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
-                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
-
-            PollingCheck.check("Ime insets should be visible", TIMEOUT,
-                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
-            expectImeVisible(TIMEOUT);
+            try (ChildWindowHolder childWindow = createChildTransparentApplicationWindowOnMain(
+                    activity, 200 /* width */, 200 /* height */,
+                    FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM,
+                    WindowInsets.Type.ime() | WindowInsets.Type.statusBars()
+                            | WindowInsets.Type.navigationBars())) {
+                // The window will be shown above (in y-axis) the IME.
+                TestUtils.runOnMainSync(
+                        () -> childWindow.getRootView().setVisibility(View.VISIBLE));
+                TestUtils.waitOnMainUntil(
+                        () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+                        TIMEOUT, "Ime insets should be visible");
+                expectImeVisible(TIMEOUT);
+            }
         }
     }
 
@@ -171,26 +163,22 @@
                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
             expectImeVisible(TIMEOUT);
 
-            final View[] childViewRoot = new View[1];
-            TestUtils.runOnMainSync(() -> {
-                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-                attrs.type = TYPE_APPLICATION_PANEL;
-                attrs.width = MATCH_PARENT;
-                attrs.height = NEW_KEYBOARD_HEIGHT;
-                attrs.gravity = Gravity.BOTTOM;
-                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM;
-                childViewRoot[0] = addChildWindow(activity, attrs);
-                childViewRoot[0].setBackgroundColor(Color.RED);
-                childViewRoot[0].setVisibility(View.VISIBLE);
-            });
-            // The window will be shown above (in y-axis) the IME.
-            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
-                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
-            // IME should be on screen without reset.
-            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
-            PollingCheck.check("Ime insets should be visible", TIMEOUT,
-                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
-            expectImeVisible(TIMEOUT);
+            try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
+                    MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
+                    FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM)) {
+                // The window will be shown above (in y-axis) the IME.
+                TestUtils.runOnMainSync(() -> {
+                    childWindow.getRootView().setBackgroundColor(Color.RED);
+                    childWindow.getRootView().setVisibility(View.VISIBLE);
+                });
+                // IME should be on screen without reset.
+                notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+                TestUtils.waitOnMainUntil(
+                        () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+                        TIMEOUT, "Ime insets should be visible");
+                expectImeVisible(TIMEOUT);
+            }
         }
     }
 
@@ -220,26 +208,22 @@
                     () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
             expectImeVisible(TIMEOUT);
 
-            final View[] childViewRoot = new View[1];
-            TestUtils.runOnMainSync(() -> {
-                final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
-                attrs.type = TYPE_APPLICATION_PANEL;
-                attrs.width = MATCH_PARENT;
-                attrs.height = NEW_KEYBOARD_HEIGHT;
-                attrs.gravity = Gravity.BOTTOM;
-                attrs.flags = FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN;
-                childViewRoot[0] = addChildWindow(activity, attrs);
-                childViewRoot[0].setBackgroundColor(Color.RED);
-                childViewRoot[0].setVisibility(View.VISIBLE);
-            });
-            // The window will be shown behind (in z-axis) the IME.
-            TestUtils.waitOnMainUntil(() -> childViewRoot[0] != null
-                    && childViewRoot[0].getVisibility() == View.VISIBLE, TIMEOUT);
-            // IME should be on screen without reset.
-            notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
-            PollingCheck.check("Ime insets should be visible", TIMEOUT,
-                    () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()));
-            expectImeVisible(TIMEOUT);
+            try (ChildWindowHolder childWindow = createChildBottomPanelWindowOnMain(activity,
+                    MATCH_PARENT /* width */, NEW_KEYBOARD_HEIGHT /* height */,
+                    FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM | FLAG_LAYOUT_IN_SCREEN)) {
+                // The window will be shown behind (in z-axis) the IME.
+                TestUtils.runOnMainSync(() -> {
+                    childWindow.getRootView().setBackgroundColor(Color.RED);
+                    childWindow.getRootView().setVisibility(View.VISIBLE);
+                });
+                // IME should be on screen without reset.
+                notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+                TestUtils.waitOnMainUntil(
+                        () -> editText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()),
+                        TIMEOUT, "Ime insets should be visible");
+                expectImeVisible(TIMEOUT);
+            }
         }
     }
 
@@ -448,11 +432,64 @@
         return new Pair<>(focusedEditTextRef.get(), testActivityRef.get());
     }
 
-    private View addChildWindow(Activity activity, WindowManager.LayoutParams attrs) {
-        final WindowManager wm = activity.getSystemService(WindowManager.class);
-        final View childViewRoot = new View(activity);
-        childViewRoot.setVisibility(View.GONE);
-        wm.addView(childViewRoot, attrs);
-        return childViewRoot;
+    /**
+     * A utility class to pack the root {@link View} and its clean-up operation that is compatible
+     * with {@link AutoCloseable} protocol.
+     */
+    private static final class ChildWindowHolder implements AutoCloseable {
+        @NonNull
+        private final View mRootView;
+
+        private ChildWindowHolder(@NonNull View rootView) {
+            mRootView = rootView;
+        }
+
+        @NonNull
+        @AnyThread
+        View getRootView() {
+            return mRootView;
+        }
+
+        @Override
+        public void close() {
+            TestUtils.runOnMainSync(() -> mRootView.getContext()
+                    .getSystemService(WindowManager.class).removeView(mRootView));
+        }
+    }
+
+    @NonNull
+    private ChildWindowHolder createChildBottomPanelWindowOnMain(Activity activity, int width,
+            int height, int windowFlags) {
+        return TestUtils.getOnMainSync(() -> {
+            final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+            attrs.token = null;
+            attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+            attrs.width = width;
+            attrs.height = height;
+            attrs.gravity = Gravity.BOTTOM;
+            attrs.flags = windowFlags;
+            final View childViewRoot = new View(activity);
+            activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
+            return new ChildWindowHolder(childViewRoot);
+        });
+    }
+
+    @NonNull
+    private ChildWindowHolder createChildTransparentApplicationWindowOnMain(Activity activity,
+            int width, int height, int windowFlags, int fitInsetsTypes) {
+        return TestUtils.getOnMainSync(() -> {
+            final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+            attrs.token = activity.getWindow().getAttributes().token;
+            attrs.type = WindowManager.LayoutParams.TYPE_APPLICATION;
+            attrs.width = width;
+            attrs.height = height;
+            attrs.format = PixelFormat.TRANSPARENT;
+            attrs.gravity = Gravity.NO_GRAVITY;
+            attrs.flags = windowFlags;
+            attrs.setFitInsetsTypes(fitInsetsTypes);
+            final View childViewRoot = new View(activity);
+            activity.getSystemService(WindowManager.class).addView(childViewRoot, attrs);
+            return new ChildWindowHolder(childViewRoot);
+        });
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java
deleted file mode 100644
index 2ac8a33..0000000
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionBlockingMethodTest.java
+++ /dev/null
@@ -1,956 +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.view.inputmethod.cts;
-
-import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
-
-import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.ClipDescription;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Process;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.ExtractedText;
-import android.view.inputmethod.ExtractedTextRequest;
-import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputConnectionWrapper;
-import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.cts.util.EndToEndImeTestBase;
-import android.view.inputmethod.cts.util.MockTestActivityUtil;
-import android.view.inputmethod.cts.util.TestActivity;
-import android.widget.EditText;
-import android.widget.LinearLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.LargeTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.cts.mockime.ImeCommand;
-import com.android.cts.mockime.ImeEvent;
-import com.android.cts.mockime.ImeEventStream;
-import com.android.cts.mockime.ImeSettings;
-import com.android.cts.mockime.MockImeSession;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Function;
-
-/**
- * Ensures that blocking APIs in {@link InputConnection} are working as expected.
- *
- * <p>TODO(b/129012881): Reduce boilerplate code.</p>
- */
-@LargeTest
-@RunWith(AndroidJUnit4.class)
-public class InputConnectionBlockingMethodTest extends EndToEndImeTestBase {
-    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
-    private static final long LONG_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
-    private static final long IMMEDIATE_TIMEOUT_NANO = TimeUnit.MILLISECONDS.toNanos(200);
-
-    private static final String TEST_MARKER_PREFIX =
-            "android.view.inputmethod.cts.InputConnectionBlockingMethodTest";
-
-    private static String getTestMarker() {
-        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
-    }
-
-    /**
-     * A utility method to verify a method is called within a certain timeout period then block
-     * it by {@link BlockingMethodVerifier#close()} is called.
-     */
-    private static final class BlockingMethodVerifier implements AutoCloseable {
-        private final CountDownLatch mWaitUntilMethodCalled = new CountDownLatch(1);
-        private final CountDownLatch mWaitUntilTestFinished = new CountDownLatch(1);
-
-        /**
-         * Used to notify when a method to be tested is called.
-         */
-        void onMethodCalled() {
-            try {
-                mWaitUntilMethodCalled.countDown();
-                mWaitUntilTestFinished.await();
-            } catch (InterruptedException e) {
-            }
-        }
-
-        /**
-         * Ensures that the method to be tested is called within {@param timeout}.
-         *
-         * @param message Message to be shown when the method is not called despite the expectation.
-         * @param timeout Timeout in milliseconds.
-         */
-        void expectMethodCalled(@NonNull String message, long timeout) {
-            try {
-                assertTrue(message, mWaitUntilMethodCalled.await(timeout, TimeUnit.MILLISECONDS));
-            } catch (InterruptedException e) {
-                fail(message + e);
-            }
-        }
-
-        /**
-         * Unblock the method to be tested to avoid the test from being blocked forever.
-         */
-        @Override
-        public void close() throws Exception {
-            mWaitUntilTestFinished.countDown();
-        }
-    }
-
-    /**
-     * A test procedure definition for
-     * {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}.
-     */
-    @FunctionalInterface
-    interface TestProcedure {
-        /**
-         * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
-         *
-         * @param session {@link MockImeSession} to be used during this test.
-         * @param stream {@link ImeEventStream} associated with {@code session}.
-         */
-        void run(@NonNull MockImeSession session, @NonNull ImeEventStream stream) throws Exception;
-    }
-
-    /**
-     * Tries to trigger {@link com.android.cts.mockime.MockIme#onUnbindInput()} by showing another
-     * Activity in a different process.
-     */
-    private void triggerUnbindInput() {
-        final boolean isInstant = InstrumentationRegistry.getInstrumentation().getTargetContext()
-                .getPackageManager().isInstantApp();
-        MockTestActivityUtil.launchSync(isInstant, TIMEOUT);
-    }
-
-    /**
-     * A utility method to run a unit test for {@link InputConnection}.
-     *
-     * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
-     * {@link InputConnection}.</p>
-     *
-     * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
-     *                                       original {@link InputConnection}.
-     * @param testProcedure Test body.
-     */
-    private void testInputConnection(
-            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
-            TestProcedure testProcedure) throws Exception {
-        testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
-    }
-
-    /**
-     * A utility method to run a unit test for {@link InputConnection}.
-     *
-     * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
-     * {@link InputConnection}.</p>
-     *
-     * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
-     *                                       original {@link InputConnection}.
-     * @param testProcedure Test body.
-     * @param closeable {@link AutoCloseable} object to be cleaned up after running test.
-     */
-    private void testInputConnection(
-            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
-            TestProcedure testProcedure, @Nullable AutoCloseable closeable) throws Exception {
-        try (AutoCloseable closeableHolder = closeable;
-             MockImeSession imeSession = MockImeSession.create(
-                     InstrumentationRegistry.getInstrumentation().getContext(),
-                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
-                     new ImeSettings.Builder())) {
-            final AtomicBoolean isTestRunning = new AtomicBoolean(true);
-            try {
-                final ImeEventStream stream = imeSession.openEventStream();
-
-                final String marker = getTestMarker();
-                TestActivity.startSync(activity -> {
-                    final LinearLayout layout = new LinearLayout(activity);
-                    layout.setOrientation(LinearLayout.VERTICAL);
-                    final EditText editText = new EditText(activity) {
-                        @Override
-                        public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
-                            final InputConnection ic = super.onCreateInputConnection(outAttrs);
-                            // Fall back to the original InputConnection once the test is done.
-                            return isTestRunning.get()
-                                    ? inputConnectionWrapperProvider.apply(ic) : ic;
-                        }
-                    };
-                    editText.setPrivateImeOptions(marker);
-                    editText.setHint("editText");
-                    editText.requestFocus();
-
-                    layout.addView(editText);
-                    activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
-                    return layout;
-                });
-
-                // Wait until the MockIme gets bound to the TestActivity.
-                expectBindInput(stream, Process.myPid(), TIMEOUT);
-
-                // Wait until "onStartInput" gets called for the EditText.
-                expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
-
-                testProcedure.run(imeSession, stream);
-            } finally {
-                isTestRunning.set(false);
-            }
-        }
-    }
-
-    /**
-     * Ensures that {@code event}'s elapse time is less than the given threshold.
-     *
-     * @param event {@link ImeEvent} to be tested.
-     * @param elapseNanoTimeThreshold threshold in nano sec.
-     */
-    private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
-            long elapseNanoTimeThreshold) {
-        final long elapseNanoTime = event.getExitTimestamp() - event.getEnterTimestamp();
-        if (elapseNanoTime > elapseNanoTimeThreshold) {
-            fail(event.getEventName() + " took " + elapseNanoTime + " nsec,"
-                    + " which must be less than" + elapseNanoTimeThreshold + " nsec.");
-        }
-    }
-
-    /**
-     * Test {@link InputConnection#getTextAfterCursor(int, int)} works as expected.
-     */
-    @Test
-    public void testGetTextAfterCursor() throws Exception {
-        final int expectedN = 3;
-        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
-        final String expectedResult = "89";
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextAfterCursor(int n, int flags) {
-                assertEquals(expectedN, n);
-                assertEquals(expectedFlags, flags);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
-            final CharSequence result =
-                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
-            assertEquals(expectedResult, result);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getTextAfterCursor(int, int)} fails after a system-defined
-     * time-out even if the target app does not respond.
-     */
-    @Test
-    public void testGetTextAfterCursorFailWithTimeout() throws Exception {
-        final String unexpectedResult = "89";
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextAfterCursor(int n, int flags) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetTextAfterCursor(
-                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES);
-            blocker.expectMethodCalled("IC#getTextAfterCursor() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertTrue("When timeout happens, IC#getTextAfterCursor() returns null",
-                    result.isNullReturnValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#getTextAfterCursor(int, int)} fail-fasts once unbindInput() is
-     * issued.
-     */
-    @Test
-    public void testGetTextAfterCursorFailFastAfterUnbindInput() throws Exception {
-        final String unexpectedResult = "89";
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextAfterCursor(int n, int flags) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callGetTextAfterCursor(
-                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
-            assertTrue("Once unbindInput() happened, IC#getTextAfterCursor() returns null",
-                    result.isNullReturnValue());
-            assertFalse("Once unbindInput() happened, IC#getTextAfterCursor() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getTextBeforeCursor(int, int)} works as expected.
-     */
-    @Test
-    public void testGetTextBeforeCursor() throws Exception {
-        final int expectedN = 3;
-        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
-        final String expectedResult = "123";
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextBeforeCursor(int n, int flags) {
-                assertEquals(expectedN, n);
-                assertEquals(expectedFlags, flags);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
-            final CharSequence result =
-                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
-            assertEquals(expectedResult, result);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails after a system-defined
-     * time-out even if the target app does not respond.
-     */
-    @Test
-    public void testGetTextBeforeCursorFailWithTimeout() throws Exception {
-        final String unexpectedResult = "123";
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextBeforeCursor(int n, int flags) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetTextBeforeCursor(
-                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES);
-            blocker.expectMethodCalled("IC#getTextBeforeCursor() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertTrue("When timeout happens, IC#getTextBeforeCursor() returns null",
-                    result.isNullReturnValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#getTextBeforeCursor(int, int)} fail-fasts once unbindInput() is
-     * issued.
-     */
-    @Test
-    public void testGetTextBeforeCursorFailFastAfterUnbindInput() throws Exception {
-        final String unexpectedResult = "123";
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getTextBeforeCursor(int n, int flags) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callGetTextBeforeCursor(
-                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
-            assertTrue("Once unbindInput() happened, IC#getTextBeforeCursor() returns null",
-                    result.isNullReturnValue());
-            assertFalse("Once unbindInput() happened, IC#getTextBeforeCursor() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getSelectedText(int)} works as expected.
-     */
-    @Test
-    public void testGetSelectedText() throws Exception {
-        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
-        final String expectedResult = "4567";
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getSelectedText(int flags) {
-                assertEquals(expectedFlags, flags);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetSelectedText(expectedFlags);
-            final CharSequence result =
-                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
-            assertEquals(expectedResult, result);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getSelectedText(int)} fails after a system-defined time-out even
-     * if the target app does not respond.
-     */
-    @Test
-    public void testGetSelectedTextFailWithTimeout() throws Exception {
-        final String unexpectedResult = "4567";
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getSelectedText(int flags) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command =
-                    session.callGetSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
-            blocker.expectMethodCalled("IC#getSelectedText() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertTrue("When timeout happens, IC#getSelectedText() returns null",
-                    result.isNullReturnValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#getSelectedText(int)} fail-fasts once unbindInput() is issued.
-     */
-    @Test
-    public void testGetSelectedTextFailFastAfterUnbindInput() throws Exception {
-        final String unexpectedResult = "4567";
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public CharSequence getSelectedText(int flags) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getSelectedText() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callGetSelectedText(
-                    InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
-            assertTrue("Once unbindInput() happened, IC#getSelectedText() returns null",
-                    result.isNullReturnValue());
-            assertFalse("Once unbindInput() happened, IC#getSelectedText() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getCursorCapsMode(int)} works as expected.
-     */
-    @Test
-    public void testGetCursorCapsMode() throws Exception {
-        final int expectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
-        final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
-                | TextUtils.CAP_MODE_WORDS;
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public int getCursorCapsMode(int reqModes) {
-                assertEquals(expectedReqMode, reqModes);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
-            final int result = expectCommand(stream, command, TIMEOUT).getReturnIntegerValue();
-            assertEquals(expectedResult, result);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getCursorCapsMode(int)} fails after a system-defined time-out
-     * even if the target app does not respond.
-     */
-    @Test
-    public void testGetCursorCapsModeFailWithTimeout() throws Exception {
-        final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public int getCursorCapsMode(int reqModes) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS);
-            blocker.expectMethodCalled("IC#getCursorCapsMode() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertEquals("When timeout happens, IC#getCursorCapsMode() returns 0",
-                    0, result.getReturnIntegerValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#getCursorCapsMode(int)} fail-fasts once unbindInput() is issued.
-     */
-    @Test
-    public void testGetCursorCapsModeFailFastAfterUnbindInput() throws Exception {
-        final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public int getCursorCapsMode(int reqModes) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getCursorCapsMode() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream,
-                    session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS), TIMEOUT);
-            assertEquals("Once unbindInput() happened, IC#getCursorCapsMode() returns 0",
-                    0, result.getReturnIntegerValue());
-            assertFalse("Once unbindInput() happened, IC#getCursorCapsMode() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} works as expected.
-     */
-    @Test
-    public void testGetExtractedText() throws Exception {
-        final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
-        final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
-        final ExtractedText expectedResult = ExtractedTextTest.createForTest();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-                assertEquals(expectedFlags, flags);
-                ExtractedTextRequestTest.assertTestInstance(request);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
-            final ExtractedText result =
-                    expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
-            ExtractedTextTest.assertTestInstance(result);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fails after a
-     * system-defined time-out even if the target app does not respond.
-     */
-    @Test
-    public void testGetExtractedTextFailWithTimeout() throws Exception {
-        final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callGetExtractedText(
-                    ExtractedTextRequestTest.createForTest(),
-                    InputConnection.GET_EXTRACTED_TEXT_MONITOR);
-            blocker.expectMethodCalled("IC#getExtractedText() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertTrue("When timeout happens, IC#getExtractedText() returns null",
-                    result.isNullReturnValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fail-fasts once
-     * unbindInput() is issued.
-     */
-    @Test
-    public void testGetExtractedTextFailFastAfterUnbindInput() throws Exception {
-        final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getExtractedText() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callGetExtractedText(
-                    ExtractedTextRequestTest.createForTest(),
-                    InputConnection.GET_EXTRACTED_TEXT_MONITOR), TIMEOUT);
-            assertTrue("Once unbindInput() happened, IC#getExtractedText() returns null",
-                    result.isNullReturnValue());
-            assertFalse("Once unbindInput() happened, IC#getExtractedText() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#requestCursorUpdates(int)} works as expected.
-     */
-    @Test
-    public void testRequestCursorUpdates() throws Exception {
-        final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
-        final boolean expectedResult = true;
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean requestCursorUpdates(int cursorUpdateMode) {
-                assertEquals(expectedFlags, cursorUpdateMode);
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callRequestCursorUpdates(expectedFlags);
-            assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#requestCursorUpdates(int)} fails after a system-defined time-out
-     * even if the target app does not respond.
-     */
-    @Test
-    public void testRequestCursorUpdatesFailWithTimeout() throws Exception {
-        final boolean unexpectedResult = true;
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean requestCursorUpdates(int cursorUpdateMode) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callRequestCursorUpdates(
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE);
-            blocker.expectMethodCalled("IC#requestCursorUpdates() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertFalse("When timeout happens, IC#requestCursorUpdates() returns false",
-                    result.getReturnBooleanValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#requestCursorUpdates(int)} fail-fasts once unbindInput() is
-     * issued.
-     */
-    @Test
-    public void testRequestCursorUpdatesFailFastAfterUnbindInput() throws Exception {
-        final boolean unexpectedResult = true;
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean requestCursorUpdates(int cursorUpdateMode) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#requestCursorUpdates() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callRequestCursorUpdates(
-                    InputConnection.CURSOR_UPDATE_IMMEDIATE), TIMEOUT);
-            assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() returns false",
-                    result.getReturnBooleanValue());
-            assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} works as expected.
-     */
-    @Test
-    public void testCommitContent() throws Exception {
-        final InputContentInfo expectedInputContentInfo = new InputContentInfo(
-                Uri.parse("content://com.example/path"),
-                new ClipDescription("sample content", new String[]{"image/png"}),
-                Uri.parse("https://example.com"));
-        final Bundle expectedOpt = new Bundle();
-        final String expectedOptKey = "testKey";
-        final int expectedOptValue = 42;
-        expectedOpt.putInt(expectedOptKey, expectedOptValue);
-        final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
-        final boolean expectedResult = true;
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
-                    Bundle opts) {
-                assertEquals(expectedInputContentInfo.getContentUri(),
-                        inputContentInfo.getContentUri());
-                assertEquals(expectedFlags, flags);
-                assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
-                return expectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command =
-                    session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
-            assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
-        });
-    }
-
-    /**
-     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails after a
-     * system-defined time-out even if the target app does not respond.
-     */
-    @Test
-    public void testCommitContentFailWithTimeout() throws Exception {
-        final boolean unexpectedResult = true;
-        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
-                    Bundle opts) {
-                blocker.onMethodCalled();
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            final ImeCommand command = session.callCommitContent(
-                    new InputContentInfo(Uri.parse("content://com.example/path"),
-                            new ClipDescription("sample content", new String[]{"image/png"}),
-                            Uri.parse("https://example.com")), 0, null);
-            blocker.expectMethodCalled("IC#commitContent() must be called back", TIMEOUT);
-            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
-            assertFalse("When timeout happens, IC#commitContent() returns false",
-                    result.getReturnBooleanValue());
-        }, blocker);
-    }
-
-    /**
-     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fail-fasts once
-     * unbindInput() is issued.
-     */
-    @Test
-    public void testCommitContentFailFastAfterUnbindInput() throws Exception {
-        final boolean unexpectedResult = true;
-        final AtomicBoolean methodCalled = new AtomicBoolean(false);
-
-        final class Wrapper extends InputConnectionWrapper {
-            private Wrapper(InputConnection target) {
-                super(target, false);
-            }
-
-            @Override
-            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
-                    Bundle opts) {
-                methodCalled.set(true);
-                return unexpectedResult;
-            }
-        }
-
-        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
-            // Memorize the current InputConnection.
-            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
-
-            // Let unbindInput happen.
-            triggerUnbindInput();
-            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
-
-            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
-            final ImeEvent result = expectCommand(stream, session.callCommitContent(
-                    new InputContentInfo(Uri.parse("content://com.example/path"),
-                            new ClipDescription("sample content", new String[]{"image/png"}),
-                            Uri.parse("https://example.com")), 0, null), TIMEOUT);
-            assertFalse("Once unbindInput() happened, IC#commitContent() returns false",
-                    result.getReturnBooleanValue());
-            assertFalse("Once unbindInput() happened, IC#commitContent() fails fast.",
-                    methodCalled.get());
-            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
-        });
-    }
-}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
new file mode 100644
index 0000000..28e9f92
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionEndToEndTest.java
@@ -0,0 +1,3891 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod.cts;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+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 static org.junit.Assert.fail;
+
+import android.content.ClipDescription;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Process;
+import android.os.SystemClock;
+import android.text.Annotation;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextSnapshot;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.MockTestActivityUtil;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.inputmethod.LegacyImeClientTestUtils;
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import com.google.common.truth.Correspondence;
+
+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.Objects;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Provides basic tests for APIs defined in {@link InputConnection}.
+ *
+ * <p>TODO(b/193535269): Clean up boilerplate code around mocking InputConnection.</p>
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionEndToEndTest extends EndToEndImeTestBase {
+    private static final long TIME_SLICE = TimeUnit.MILLISECONDS.toMillis(125);
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long EXPECTED_NOT_CALLED_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+    private static final long LONG_TIMEOUT = TimeUnit.SECONDS.toMillis(30);
+    private static final long IMMEDIATE_TIMEOUT_NANO = TimeUnit.MILLISECONDS.toNanos(200);
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.InputConnectionEndToEndTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    /**
+     * A utility method to verify a method is called within a certain timeout period then block
+     * it by {@link BlockingMethodVerifier#close()} is called.
+     */
+    private static final class BlockingMethodVerifier implements AutoCloseable {
+        private final CountDownLatch mWaitUntilMethodCalled = new CountDownLatch(1);
+        private final CountDownLatch mWaitUntilTestFinished = new CountDownLatch(1);
+
+        /**
+         * Used to notify when a method to be tested is called.
+         */
+        void onMethodCalled() {
+            try {
+                mWaitUntilMethodCalled.countDown();
+                mWaitUntilTestFinished.await();
+            } catch (InterruptedException e) {
+            }
+        }
+
+        /**
+         * Ensures that the method to be tested is called within {@param timeout}.
+         *
+         * @param message Message to be shown when the method is not called despite the expectation.
+         * @param timeout Timeout in milliseconds.
+         */
+        void expectMethodCalled(@NonNull String message, long timeout) {
+            try {
+                assertTrue(message, mWaitUntilMethodCalled.await(timeout, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                fail(message + e);
+            }
+        }
+
+        /**
+         * Unblock the method to be tested to avoid the test from being blocked forever.
+         */
+        @Override
+        public void close() throws Exception {
+            mWaitUntilTestFinished.countDown();
+        }
+    }
+
+    /**
+     * A utility method to verify that a method is called with a certain set of parameters.
+     */
+    private static final class MethodCallVerifier {
+        private final AtomicReference<Bundle> mArgs = new AtomicReference<>();
+        private final AtomicInteger mCallCount = new AtomicInteger(0);
+
+        @AnyThread
+        void reset() {
+            mArgs.set(null);
+            mCallCount.set(0);
+        }
+
+        /**
+         * Used to record when a method to be tested is called.
+         *
+         * @param argumentsRecorder a {@link Consumer} to capture method parameters.
+         */
+        void onMethodCalled(@NonNull Consumer<Bundle> argumentsRecorder) {
+            final Bundle bundle = new Bundle();
+            argumentsRecorder.accept(bundle);
+            mArgs.set(bundle);
+            mCallCount.incrementAndGet();
+        }
+
+        /**
+         * Used to assert captured parameters later.
+         *
+         * @param argumentsVerifier a {@link Consumer} to verify method arguments.
+         * @throws AssertionError when {@link #onMethodCalled(Consumer)} was not called only once.
+         */
+        void assertCalledOnce(@NonNull Consumer<Bundle> argumentsVerifier) {
+            assertEquals(1, mCallCount.get());
+            final Bundle bundle = mArgs.get();
+            assertNotNull(bundle);
+            argumentsVerifier.accept(bundle);
+        }
+
+        /**
+         * Ensures that the method to be tested is called within {@param timeout}.
+         *
+         * @param argumentsVerifier a {@link Consumer} to verify method arguments.
+         * @param timeout timeout in millisecond
+         * @throws AssertionError when {@link #onMethodCalled(Consumer)} was not called only once.
+         */
+        void expectCalledOnce(@NonNull Consumer<Bundle> argumentsVerifier, long timeout) {
+            // Currently using busy-wait because CountDownLatch is not compatible with reset().
+            // TODO: Consider using other more efficient operation.
+            long remainingTime = timeout;
+            while (mCallCount.get() == 0) {
+                if (remainingTime < 0) {
+                    fail("The method must be called, but was not within" + timeout + " msec.");
+                }
+                SystemClock.sleep(TIME_SLICE);
+                remainingTime -= TIME_SLICE;
+            }
+            assertEquals(1, mCallCount.get());
+            final Bundle bundle = mArgs.get();
+            assertNotNull(bundle);
+            argumentsVerifier.accept(bundle);
+        }
+
+        /**
+         * Used to assert that {@link #onMethodCalled(Consumer)} was never called.
+         *
+         * @param callCountVerificationMessage A message to be used when the assertion fails.
+         */
+        void assertNotCalled(@Nullable String callCountVerificationMessage) {
+            if (callCountVerificationMessage != null) {
+                assertEquals(callCountVerificationMessage, 0, mCallCount.get());
+            } else {
+                assertEquals(0, mCallCount.get());
+            }
+        }
+
+        /**
+         * Ensures that the method to be tested is not called within {@param timeout}.
+         *
+         * @param callCountVerificationMessage A message to be used when the assertion fails.
+         * @param timeout timeout in millisecond
+         */
+        void expectNotCalled(@Nullable String callCountVerificationMessage, long timeout) {
+            // Currently using busy-wait because CountDownLatch is not compatible with reset().
+            // TODO: Consider using other more efficient operation.
+            long remainingTime = timeout;
+            while (true) {
+                if (mCallCount.get() != 0) {
+                    fail("The method must not be called. params=" + evaluateBundle(mArgs.get()));
+                }
+                if (remainingTime < 0) {
+                    break;  // This is indeed an expected scenario, not an error.
+                }
+                SystemClock.sleep(TIME_SLICE);
+                remainingTime -= TIME_SLICE;
+            }
+            if (callCountVerificationMessage != null) {
+                assertEquals(callCountVerificationMessage, 0, mCallCount.get());
+            } else {
+                assertEquals(0, mCallCount.get());
+            }
+        }
+
+        /**
+         * Recursively evaluate {@link Bundle} so that {@link Bundle#toString()} can print all the
+         * nested {@link Bundle} objects.
+         *
+         * @param bundle {@link Bundle} to recursively evaluate.
+         * @return the {@code bundle} object passed.
+         */
+        @Nullable
+        private static Bundle evaluateBundle(@Nullable Bundle bundle) {
+            if (bundle != null) {
+                for (String key : bundle.keySet()) {
+                    final Object value = bundle.get(key);
+                    if (value instanceof Bundle) {
+                        evaluateBundle((Bundle) value);
+                    }
+                }
+            }
+            return bundle;
+        }
+    }
+
+    /**
+     * A test procedure definition for
+     * {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}.
+     */
+    @FunctionalInterface
+    interface TestProcedure {
+        /**
+         * The test body of {@link #testInputConnection(Function, TestProcedure, AutoCloseable)}
+         *
+         * @param session {@link MockImeSession} to be used during this test.
+         * @param stream {@link ImeEventStream} associated with {@code session}.
+         */
+        void run(@NonNull MockImeSession session, @NonNull ImeEventStream stream) throws Exception;
+    }
+
+    /**
+     * Tries to trigger {@link com.android.cts.mockime.MockIme#onUnbindInput()} by showing another
+     * Activity in a different process.
+     */
+    private void triggerUnbindInput() {
+        final boolean isInstant = InstrumentationRegistry.getInstrumentation().getTargetContext()
+                .getPackageManager().isInstantApp();
+        MockTestActivityUtil.launchSync(isInstant, TIMEOUT);
+    }
+
+    /**
+     * A utility method to run a unit test for {@link InputConnection}.
+     *
+     * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
+     * {@link InputConnection}.</p>
+     *
+     * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
+     *                                       original {@link InputConnection}.
+     * @param testProcedure Test body.
+     */
+    private void testInputConnection(
+            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+            TestProcedure testProcedure) throws Exception {
+        testInputConnection(inputConnectionWrapperProvider, testProcedure, null);
+    }
+
+    /**
+     * A utility method to run a unit test for {@link InputConnection} that is as-if built with
+     * {@link android.os.Build.VERSION_CODES#CUPCAKE} SDK.
+     *
+     * <p>This helps you to test the situation where IMEs' calling newly added
+     * {@link InputConnection} APIs would be fallen back to its default interface method or could be
+     * causing {@link java.lang.AbstractMethodError} unless specially handled.
+     *
+     * @param testProcedure Test body.
+     */
+    private void testMinimallyImplementedInputConnection(TestProcedure testProcedure)
+            throws Exception {
+        testInputConnection(
+                ic -> LegacyImeClientTestUtils.createMinimallyImplementedNoOpInputConnection(),
+                testProcedure, null);
+    }
+
+    /**
+     * A utility method to run a unit test for {@link InputConnection}.
+     *
+     * <p>This utility method enables you to avoid boilerplate code when writing unit tests for
+     * {@link InputConnection}.</p>
+     *
+     * @param inputConnectionWrapperProvider {@link Function} to install custom hooks to the
+     *                                       original {@link InputConnection}.
+     * @param testProcedure Test body.
+     * @param closeable {@link AutoCloseable} object to be cleaned up after running test.
+     */
+    private void testInputConnection(
+            Function<InputConnection, InputConnection> inputConnectionWrapperProvider,
+            TestProcedure testProcedure, @Nullable AutoCloseable closeable) throws Exception {
+        try (AutoCloseable closeableHolder = closeable;
+             MockImeSession imeSession = MockImeSession.create(
+                     InstrumentationRegistry.getInstrumentation().getContext(),
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+                // injecting our custom InputConnection implementation.
+                final EditText editText = new EditText(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return imeSession.isActive();
+                    }
+
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        if (imeSession.isActive()) {
+                            final InputConnection ic = super.onCreateInputConnection(outAttrs);
+                            return inputConnectionWrapperProvider.apply(ic);
+                        }
+                        return null;
+                    }
+                };
+
+                editText.setPrivateImeOptions(marker);
+                editText.setHint("editText");
+                editText.requestFocus();
+
+                layout.addView(editText);
+                activity.getWindow().setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+                return layout;
+            });
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Wait until "onStartInput" gets called for the EditText.
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            testProcedure.run(imeSession, stream);
+        }
+    }
+
+    /**
+     * Ensures that {@code event}'s elapse time is less than the given threshold.
+     *
+     * @param event {@link ImeEvent} to be tested.
+     * @param elapseNanoTimeThreshold threshold in nano sec.
+     */
+    private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
+            long elapseNanoTimeThreshold) {
+        final long elapseNanoTime = event.getExitTimestamp() - event.getEnterTimestamp();
+        if (elapseNanoTime > elapseNanoTimeThreshold) {
+            fail(event.getEventName() + " took " + elapseNanoTime + " nsec,"
+                    + " which must be less than" + elapseNanoTimeThreshold + " nsec.");
+        }
+    }
+
+    @Nullable
+    private static CharSequence createTestCharSequence(@Nullable String text,
+            @Nullable Annotation annotation) {
+        if (text == null) {
+            return null;
+        }
+        final SpannableStringBuilder sb = new SpannableStringBuilder(text);
+        if (annotation != null) {
+            sb.setSpan(annotation, 0, sb.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+        }
+        return sb;
+    }
+
+    private static void assertEqualsForTestCharSequence(@Nullable CharSequence expected,
+            @Nullable CharSequence actual) {
+        assertEquals(Objects.toString(expected), Objects.toString(actual));
+        final Function<CharSequence, List<Annotation>> toAnnotations = cs -> {
+            if (cs instanceof Spanned) {
+                final Spanned spanned = (Spanned) cs;
+                return Arrays.asList(spanned.getSpans(0, cs.length(), Annotation.class));
+            }
+            return Collections.emptyList();
+        };
+        assertThat(toAnnotations.apply(actual)).comparingElementsUsing(Correspondence.transforming(
+                (Annotation annotation) -> Pair.create(annotation.getKey(), annotation.getValue()),
+                (Annotation annotation) -> Pair.create(annotation.getKey(), annotation.getValue()),
+                "has the same Key/Value as"))
+                .containsExactlyElementsIn(toAnnotations.apply(expected));
+    }
+
+    /**
+     * Test {@link InputConnection#getTextAfterCursor(int, int)} works as expected.
+     */
+    @Test
+    public void testGetTextAfterCursor() throws Exception {
+        final int expectedN = 3;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final CharSequence expectedResult =
+                createTestCharSequence("89", new Annotation("command", "getTextAfterCursor"));
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
+            final CharSequence result =
+                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+            assertEqualsForTestCharSequence(expectedResult, result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedN, args.get("n"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getTextAfterCursor(int, int)} fails when a negative
+     * {@code length} is passed.  See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetTextAfterCursorFailWithNegativeLength() throws Exception {
+        final String unexpectedResult = "123";
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextAfterCursor(-1, 0);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("IC#getTextAfterCursor() returns null for a negative length.",
+                    result.isNullReturnValue());
+            methodCallVerifier.expectNotCalled(
+                    "IC#getTextAfterCursor() will not be triggered with a negative length.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getTextAfterCursor(int, int)} fails after a system-defined
+     * time-out even if the target app does not respond.
+     */
+    @Test
+    public void testGetTextAfterCursorFailWithTimeout() throws Exception {
+        final int expectedN = 3;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final String unexpectedResult = "89";
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextAfterCursor(expectedN, expectedFlags);
+            blocker.expectMethodCalled("IC#getTextAfterCursor() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("When timeout happens, IC#getTextAfterCursor() returns null",
+                    result.isNullReturnValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedN, args.get("n"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getTextAfterCursor(int, int)} fail-fasts once unbindInput() is
+     * issued.
+     */
+    @Test
+    public void testGetTextAfterCursorFailFastAfterUnbindInput() throws Exception {
+        final String unexpectedResult = "89";
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextAfterCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callGetTextAfterCursor(
+                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+            assertTrue("Once unbindInput() happened, IC#getTextAfterCursor() returns null",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getTextAfterCursor() fails fast.");
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getTextBeforeCursor(int, int)} works as expected.
+     */
+    @Test
+    public void testGetTextBeforeCursor() throws Exception {
+        final int expectedN = 3;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final CharSequence expectedResult =
+                createTestCharSequence("123", new Annotation("command", "getTextBeforeCursor"));
+
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
+            final CharSequence result =
+                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+            assertEqualsForTestCharSequence(expectedResult, result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedN, args.get("n"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails when a negative
+     * {@code length} is passed.  See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetTextBeforeCursorFailWithNegativeLength() throws Exception {
+        final String unexpectedResult = "123";
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextBeforeCursor(-1, 0);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("IC#getTextBeforeCursor() returns null for a negative length.",
+                    result.isNullReturnValue());
+            methodCallVerifier.expectNotCalled(
+                    "IC#getTextBeforeCursor() will not be triggered with a negative length.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getTextBeforeCursor(int, int)} fails after a system-defined
+     * time-out even if the target app does not respond.
+     */
+    @Test
+    public void testGetTextBeforeCursorFailWithTimeout() throws Exception {
+        final int expectedN = 3;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final String unexpectedResult = "123";
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetTextBeforeCursor(expectedN, expectedFlags);
+            blocker.expectMethodCalled("IC#getTextBeforeCursor() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("When timeout happens, IC#getTextBeforeCursor() returns null",
+                    result.isNullReturnValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedN, args.get("n"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getTextBeforeCursor(int, int)} fail-fasts once unbindInput() is
+     * issued.
+     */
+    @Test
+    public void testGetTextBeforeCursorFailFastAfterUnbindInput() throws Exception {
+        final String unexpectedResult = "123";
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getTextBeforeCursor(int n, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("n", n);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callGetTextBeforeCursor(
+                    unexpectedResult.length(), InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+            assertTrue("Once unbindInput() happened, IC#getTextBeforeCursor() returns null",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getTextBeforeCursor() fails fast.");
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSelectedText(int)} works as expected.
+     */
+    @Test
+    public void testGetSelectedText() throws Exception {
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final CharSequence expectedResult =
+                createTestCharSequence("4567", new Annotation("command", "getSelectedText"));
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getSelectedText(int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("flags", flags);
+                });
+                assertEquals(expectedFlags, flags);
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSelectedText(expectedFlags);
+            final CharSequence result =
+                    expectCommand(stream, command, TIMEOUT).getReturnCharSequenceValue();
+            assertEqualsForTestCharSequence(expectedResult, result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSelectedText(int)} fails after a system-defined time-out even
+     * if the target app does not respond.
+     */
+    @Test
+    public void testGetSelectedTextFailWithTimeout() throws Exception {
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final String unexpectedResult = "4567";
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getSelectedText(int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("flags", flags);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callGetSelectedText(InputConnection.GET_TEXT_WITH_STYLES);
+            blocker.expectMethodCalled("IC#getSelectedText() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("When timeout happens, IC#getSelectedText() returns null",
+                    result.isNullReturnValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getSelectedText(int)} fail-fasts once unbindInput() is issued.
+     */
+    @Test
+    public void testGetSelectedTextFailFastAfterUnbindInput() throws Exception {
+        final String unexpectedResult = "4567";
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public CharSequence getSelectedText(int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getSelectedText() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callGetSelectedText(
+                    InputConnection.GET_TEXT_WITH_STYLES), TIMEOUT);
+            assertTrue("Once unbindInput() happened, IC#getSelectedText() returns null",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getSelectedText() fails fast.");
+        });
+    }
+
+    /**
+     * Verify that {@link InputConnection#getSelectedText(int)} returns {@code null} when the target
+     * app does not implement it.  This can happen if the app was built before
+     * {@link android.os.Build.VERSION_CODES#GINGERBREAD}.
+     */
+    @Test
+    public void testGetSelectedTextFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSelectedText(0);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("Currently getSelectedText() returns null when the target app does not"
+                    + " implement it.", result.isNullReturnValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} works as expected.
+     */
+    @Test
+    public void testGetSurroundingText() throws Exception {
+        final int expectedBeforeLength = 3;
+        final int expectedAfterLength = 4;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final CharSequence expectedText =
+                createTestCharSequence("012345", new Annotation("command", "getSurroundingText"));
+        final SurroundingText expectedResult = new SurroundingText(expectedText, 1, 2, 0);
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                    int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                    args.putInt("flags", flags);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSurroundingText(expectedBeforeLength,
+                    expectedAfterLength, expectedFlags);
+            final SurroundingText result =
+                    expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
+            assertEqualsForTestCharSequence(expectedResult.getText(), result.getText());
+            assertEquals(expectedResult.getSelectionStart(), result.getSelectionStart());
+            assertEquals(expectedResult.getSelectionEnd(), result.getSelectionEnd());
+            assertEquals(expectedResult.getOffset(), result.getOffset());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedBeforeLength, args.get("beforeLength"));
+                assertEquals(expectedAfterLength, args.get("afterLength"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} fails when a nagative
+     * {@code afterLength} is passed.  See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithNegativeAfterLength() throws Exception {
+        final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                    int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSurroundingText(1, -1, 0);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("IC#getSurroundingText() returns null for a negative afterLength.",
+                    result.isNullReturnValue());
+            methodCallVerifier.expectNotCalled(
+                    "IC#getSurroundingText() will not be triggered with a negative afterLength.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} fails when a negative
+     * {@code beforeLength} is passed.  See Bug 169114026 for background.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithNegativeBeforeLength() throws Exception {
+        final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                    int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSurroundingText(-1, 1, 0);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("IC#getSurroundingText() returns null for a negative beforeLength.",
+                    result.isNullReturnValue());
+            methodCallVerifier.expectNotCalled(
+                    "IC#getSurroundingText() will not be triggered with a negative beforeLength.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} fails after a system-defined
+     * time-out even if the target app does not respond.
+     */
+    @Test
+    public void testGetSurroundingTextFailWithTimeout() throws Exception {
+        final int expectedBeforeLength = 3;
+        final int expectedAfterLength = 4;
+        final int expectedFlags = InputConnection.GET_TEXT_WITH_STYLES;
+        final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                    int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                    args.putInt("flags", flags);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSurroundingText(expectedBeforeLength,
+                    expectedAfterLength, expectedFlags);
+            blocker.expectMethodCalled("IC#getSurroundingText() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("When timeout happens, IC#getSurroundingText() returns null",
+                    result.isNullReturnValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedBeforeLength, args.get("beforeLength"));
+                assertEquals(expectedAfterLength, args.get("afterLength"));
+                assertEquals(expectedFlags, args.get("flags"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} fail-fasts once unbindInput()
+     * is issued.
+     */
+    @Test
+    public void testGetSurroundingTextFailFastAfterUnbindInput() throws Exception {
+        final int beforeLength = 3;
+        final int afterLength = 4;
+        final int flags = InputConnection.GET_TEXT_WITH_STYLES;
+        final SurroundingText unexpectedResult = new SurroundingText("012345", 1, 2, 0);
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                    int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextBeforeCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callGetSurroundingText(
+                    beforeLength, afterLength, flags), TIMEOUT);
+            assertTrue("Once unbindInput() happened, IC#getSurroundingText() returns null",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getSurroundingText() fails fast.");
+        });
+    }
+
+    /**
+     * Verify that the default implementation of
+     * {@link InputConnection#getSurroundingText(int, int, int)} returns {@code null} without any
+     * crash even when the target app does not override it .
+     */
+    @Test
+    public void testGetSurroundingTextDefaultMethod() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetSurroundingText(1, 2, 0);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("Default IC#getSurroundingText() returns null.",
+                    result.isNullReturnValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getCursorCapsMode(int)} works as expected.
+     */
+    @Test
+    public void testGetCursorCapsMode() throws Exception {
+        final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
+                | TextUtils.CAP_MODE_WORDS;
+        final int expectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_SENTENCES;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public int getCursorCapsMode(int reqModes) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("reqModes", reqModes);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
+            final int result = expectCommand(stream, command, TIMEOUT).getReturnIntegerValue();
+            assertEquals(expectedResult, result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedReqMode, args.getInt("reqModes"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getCursorCapsMode(int)} fails after a system-defined time-out
+     * even if the target app does not respond.
+     */
+    @Test
+    public void testGetCursorCapsModeFailWithTimeout() throws Exception {
+        final int expectedReqMode = TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS
+                | TextUtils.CAP_MODE_WORDS;
+        final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public int getCursorCapsMode(int reqModes) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("reqModes", reqModes);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetCursorCapsMode(expectedReqMode);
+            blocker.expectMethodCalled("IC#getCursorCapsMode() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertEquals("When timeout happens, IC#getCursorCapsMode() returns 0",
+                    0, result.getReturnIntegerValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedReqMode, args.getInt("reqModes"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getCursorCapsMode(int)} fail-fasts once unbindInput() is issued.
+     */
+    @Test
+    public void testGetCursorCapsModeFailFastAfterUnbindInput() throws Exception {
+        final int unexpectedResult = EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public int getCursorCapsMode(int reqModes) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("reqModes", reqModes);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getCursorCapsMode() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream,
+                    session.callGetCursorCapsMode(TextUtils.CAP_MODE_WORDS), TIMEOUT);
+            assertEquals("Once unbindInput() happened, IC#getCursorCapsMode() returns 0",
+                    0, result.getReturnIntegerValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getCursorCapsMode() fails fast.");
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} works as expected.
+     */
+    @Test
+    public void testGetExtractedText() throws Exception {
+        final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
+        final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
+        final ExtractedText expectedResult = ExtractedTextTest.createForTest();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("request", request);
+                    args.putInt("flags", flags);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
+            final ExtractedText result =
+                    expectCommand(stream, command, TIMEOUT).getReturnParcelableValue();
+            ExtractedTextTest.assertTestInstance(result);
+            methodCallVerifier.assertCalledOnce(args -> {
+                ExtractedTextRequestTest.assertTestInstance(args.getParcelable("request"));
+                assertEquals(expectedFlags, args.getInt("flags"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fails after a
+     * system-defined time-out even if the target app does not respond.
+     */
+    @Test
+    public void testGetExtractedTextFailWithTimeout() throws Exception {
+        final ExtractedTextRequest expectedRequest = ExtractedTextRequestTest.createForTest();
+        final int expectedFlags = InputConnection.GET_EXTRACTED_TEXT_MONITOR;
+        final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("request", request);
+                    args.putInt("flags", flags);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetExtractedText(expectedRequest, expectedFlags);
+            blocker.expectMethodCalled("IC#getExtractedText() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertTrue("When timeout happens, IC#getExtractedText() returns null",
+                    result.isNullReturnValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                ExtractedTextRequestTest.assertTestInstance(args.getParcelable("request"));
+                assertEquals(expectedFlags, args.getInt("flags"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#getExtractedText(ExtractedTextRequest, int)} fail-fasts once
+     * unbindInput() is issued.
+     */
+    @Test
+    public void testGetExtractedTextFailFastAfterUnbindInput() throws Exception {
+        final ExtractedText unexpectedResult = ExtractedTextTest.createForTest();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("request", request);
+                    args.putInt("flags", flags);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getExtractedText() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callGetExtractedText(
+                    ExtractedTextRequestTest.createForTest(),
+                    InputConnection.GET_EXTRACTED_TEXT_MONITOR), TIMEOUT);
+            assertTrue("Once unbindInput() happened, IC#getExtractedText() returns null",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#getExtractedText() fails fast.");
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#requestCursorUpdates(int)} works as expected.
+     */
+    @Test
+    public void testRequestCursorUpdates() throws Exception {
+        final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
+        final boolean expectedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean requestCursorUpdates(int cursorUpdateMode) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("cursorUpdateMode", cursorUpdateMode);
+                });
+                assertEquals(expectedFlags, cursorUpdateMode);
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callRequestCursorUpdates(expectedFlags);
+            assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedFlags, args.getInt("cursorUpdateMode"));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#requestCursorUpdates(int)} fails after a system-defined time-out
+     * even if the target app does not respond.
+     */
+    @Test
+    public void testRequestCursorUpdatesFailWithTimeout() throws Exception {
+        final int expectedFlags = InputConnection.CURSOR_UPDATE_IMMEDIATE;
+        final boolean unexpectedResult = true;
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean requestCursorUpdates(int cursorUpdateMode) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("cursorUpdateMode", cursorUpdateMode);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callRequestCursorUpdates(
+                    InputConnection.CURSOR_UPDATE_IMMEDIATE);
+            blocker.expectMethodCalled("IC#requestCursorUpdates() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertFalse("When timeout happens, IC#requestCursorUpdates() returns false",
+                    result.getReturnBooleanValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                assertEquals(expectedFlags, args.getInt("cursorUpdateMode"));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#requestCursorUpdates(int)} fail-fasts once unbindInput() is
+     * issued.
+     */
+    @Test
+    public void testRequestCursorUpdatesFailFastAfterUnbindInput() throws Exception {
+        final boolean unexpectedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean requestCursorUpdates(int cursorUpdateMode) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("cursorUpdateMode", cursorUpdateMode);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#requestCursorUpdates() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callRequestCursorUpdates(
+                    InputConnection.CURSOR_UPDATE_IMMEDIATE), TIMEOUT);
+            assertFalse("Once unbindInput() happened, IC#requestCursorUpdates() returns false",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#requestCursorUpdates() fails fast.");
+        });
+    }
+
+    /**
+     * Verify that {@link InputConnection#requestCursorUpdates(int)} fails when the target app does
+     * not implement it. This can happen if the app was built before
+     * {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+     */
+    @Test
+    public void testRequestCursorUpdatesFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callRequestCursorUpdates(
+                    InputConnection.CURSOR_UPDATE_IMMEDIATE);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertFalse("IC#requestCursorUpdates() returns false when the target app does not "
+                    + " implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} works as expected.
+     */
+    @Test
+    public void testCommitContent() throws Exception {
+        final InputContentInfo expectedInputContentInfo = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+        final Bundle expectedOpt = new Bundle();
+        final String expectedOptKey = "testKey";
+        final int expectedOptValue = 42;
+        expectedOpt.putInt(expectedOptKey, expectedOptValue);
+        final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+        final boolean expectedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+                    Bundle opts) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("inputContentInfo", inputContentInfo);
+                    args.putInt("flags", flags);
+                    args.putBundle("opts", opts);
+                });
+                return expectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
+            assertTrue(expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                final InputContentInfo inputContentInfo = args.getParcelable("inputContentInfo");
+                final Bundle opts = args.getBundle("opts");
+                assertNotNull(inputContentInfo);
+                assertEquals(expectedInputContentInfo.getContentUri(),
+                        inputContentInfo.getContentUri());
+                assertEquals(expectedFlags, args.getInt("flags"));
+                assertNotNull(opts);
+                assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
+            });
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails after a
+     * system-defined time-out even if the target app does not respond.
+     */
+    @Test
+    public void testCommitContentFailWithTimeout() throws Exception {
+        final InputContentInfo expectedInputContentInfo = new InputContentInfo(
+                Uri.parse("content://com.example/path"),
+                new ClipDescription("sample content", new String[]{"image/png"}),
+                Uri.parse("https://example.com"));
+        final Bundle expectedOpt = new Bundle();
+        final String expectedOptKey = "testKey";
+        final int expectedOptValue = 42;
+        expectedOpt.putInt(expectedOptKey, expectedOptValue);
+        final int expectedFlags = InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION;
+        final boolean unexpectedResult = true;
+        final BlockingMethodVerifier blocker = new BlockingMethodVerifier();
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+                    Bundle opts) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("inputContentInfo", inputContentInfo);
+                    args.putInt("flags", flags);
+                    args.putBundle("opts", opts);
+                });
+                blocker.onMethodCalled();
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callCommitContent(expectedInputContentInfo, expectedFlags, expectedOpt);
+            blocker.expectMethodCalled("IC#commitContent() must be called back", TIMEOUT);
+            final ImeEvent result = expectCommand(stream, command, LONG_TIMEOUT);
+            assertFalse("When timeout happens, IC#commitContent() returns false",
+                    result.getReturnBooleanValue());
+            methodCallVerifier.assertCalledOnce(args -> {
+                final InputContentInfo inputContentInfo = args.getParcelable("inputContentInfo");
+                final Bundle opts = args.getBundle("opts");
+                assertNotNull(inputContentInfo);
+                assertEquals(expectedInputContentInfo.getContentUri(),
+                        inputContentInfo.getContentUri());
+                assertEquals(expectedFlags, args.getInt("flags"));
+                assertNotNull(opts);
+                assertEquals(expectedOpt.getInt(expectedOptKey), opts.getInt(expectedOptKey));
+            });
+        }, blocker);
+    }
+
+    /**
+     * Test {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fail-fasts once
+     * unbindInput() is issued.
+     */
+    @Test
+    public void testCommitContentFailFastAfterUnbindInput() throws Exception {
+        final boolean unexpectedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitContent(InputContentInfo inputContentInfo, int flags,
+                    Bundle opts) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("inputContentInfo", inputContentInfo);
+                    args.putInt("flags", flags);
+                    args.putBundle("opts", opts);
+                });
+                return unexpectedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream, session.callCommitContent(
+                    new InputContentInfo(Uri.parse("content://com.example/path"),
+                            new ClipDescription("sample content", new String[]{"image/png"}),
+                            Uri.parse("https://example.com")), 0, null), TIMEOUT);
+            assertFalse("Once unbindInput() happened, IC#commitContent() returns false",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+            methodCallVerifier.assertNotCalled(
+                    "Once unbindInput() happened, IC#commitContent() fails fast.");
+        });
+    }
+
+    /**
+     * Verify that {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} fails when
+     * the target app does not implement it. This can happen if the app was built before
+     * {@link android.os.Build.VERSION_CODES#N_MR1}.
+     */
+    @Test
+    public void testCommitContentFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCommitContent(
+                    new InputContentInfo(Uri.parse("content://com.example/path"),
+                            new ClipDescription("sample content", new String[]{"image/png"}),
+                            Uri.parse("https://example.com")), 0, null);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertFalse("Currently IC#commitContent() returns false when the target app does not"
+                    + " implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#deleteSurroundingText(int, int)} works as expected.
+     */
+    @Test
+    public void testDeleteSurroundingText() throws Exception {
+        final int expectedBeforeLength = 5;
+        final int expectedAfterLength = 4;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callDeleteSurroundingText(expectedBeforeLength, expectedAfterLength);
+            assertTrue("deleteSurroundingText() always returns true unless RemoteException is"
+                    + " thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedBeforeLength, args.getInt("beforeLength"));
+                assertEquals(expectedAfterLength, args.getInt("afterLength"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#deleteSurroundingText(int, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testDeleteSurroundingTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#deleteSurroundingText() for the memorized IC should fail fast.
+            final ImeCommand command = session.callDeleteSurroundingText(3, 4);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#deleteSurroundingText() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#deleteSurroundingText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} works as expected.
+     */
+    @Test
+    public void testDeleteSurroundingTextInCodePoints() throws Exception {
+        final int expectedBeforeLength = 5;
+        final int expectedAfterLength = 4;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(
+                    expectedBeforeLength, expectedAfterLength);
+            assertTrue("deleteSurroundingText() always returns true unless RemoteException is"
+                    + " thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedBeforeLength, args.getInt("beforeLength"));
+                assertEquals(expectedAfterLength, args.getInt("afterLength"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testDeleteSurroundingTextInCodePointsAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("beforeLength", beforeLength);
+                    args.putInt("afterLength", afterLength);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#deleteSurroundingTextInCodePoints() for the memorized IC should fail fast.
+            final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(3, 4);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#deleteSurroundingTextInCodePoints() still returns true even"
+                    + " after unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#deleteSurroundingTextInCodePoints() fails"
+                    + " fast.", EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that the app does not crash even if it does not implement
+     * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}, which can happen if the
+     * app was built before {@link android.os.Build.VERSION_CODES#N}.
+     */
+    @Test
+    public void testDeleteSurroundingTextInCodePointsFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callDeleteSurroundingTextInCodePoints(1, 2);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#deleteSurroundingTextInCodePoints() returns true even when the target"
+                    + " app does not implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitText(CharSequence, int)} works as expected.
+     */
+    @Test
+    public void testCommitText() throws Exception {
+        final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+        final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+        final int expectedNewCursorPosition = 123;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitText(CharSequence text, int newCursorPosition) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                });
+
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callCommitText(expectedText, expectedNewCursorPosition);
+            assertTrue("commitText() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+                assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitText(CharSequence, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testCommitTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitText(CharSequence text, int newCursorPosition) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream,
+                    session.callCommitText("text", 1), TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#commitText() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#commitText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitText(CharSequence, int, TextAttribute)} works as expected.
+     */
+    @Test
+    public void testCommitTextWithTextAttribute() throws Exception {
+        final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+        final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+        final int expectedNewCursorPosition = 123;
+        final ArrayList<String> expectedSuggestions = new ArrayList<>();
+        expectedSuggestions.add("test");
+        final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+                .setTextConversionSuggestions(expectedSuggestions).build();
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitText(
+                    CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCommitText(
+                    expectedText, expectedNewCursorPosition, expectedTextAttribute);
+            assertTrue("commitText() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+                assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+                final TextAttribute textAttribute = args.getParcelable("textAttribute");
+                assertThat(textAttribute).isNotNull();
+                assertThat(textAttribute.getTextConversionSuggestions())
+                        .containsExactlyElementsIn(expectedSuggestions);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitText(CharSequence, int, TextAttribute)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testCommitTextAfterUnbindInputWithTextAttribute() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitText(
+                    CharSequence text, int newCursorPosition, TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now IC#getTextAfterCursor() for the memorized IC should fail fast.
+            final ImeEvent result = expectCommand(stream,
+                    session.callCommitText("text", 1,
+                            new TextAttribute.Builder().setTextConversionSuggestions(
+                                    Collections.singletonList("test")).build()),
+                    TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#commitText() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#commitText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingText(CharSequence, int)} works as expected.
+     */
+    @Test
+    public void testSetComposingText() throws Exception {
+        final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+        final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+        final int expectedNewCursorPosition = 123;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingText(CharSequence text, int newCursorPosition) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callSetComposingText(expectedText, expectedNewCursorPosition);
+            assertTrue("setComposingText() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+                assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingText(CharSequence, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetComposingTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingText(CharSequence text, int newCursorPosition) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetComposingText("text", 1);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setComposingText() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setComposingText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)}
+     * works as expected.
+     */
+    @Test
+    public void testSetComposingTextWithTextAttribute() throws Exception {
+        final Annotation expectedSpan = new Annotation("expectedKey", "expectedValue");
+        final CharSequence expectedText = createTestCharSequence("expectedText", expectedSpan);
+        final int expectedNewCursorPosition = 123;
+        final ArrayList<String> expectedSuggestions = new ArrayList<>();
+        expectedSuggestions.add("test");
+        final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+                .setTextConversionSuggestions(expectedSuggestions).build();
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingText(CharSequence text, int newCursorPosition,
+                    TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetComposingText(
+                    expectedText, expectedNewCursorPosition, expectedTextAttribute);
+            assertTrue("testSetComposingTextWithTextAttribute() always returns true unless"
+                            + " RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEqualsForTestCharSequence(expectedText, args.getCharSequence("text"));
+                assertEquals(expectedNewCursorPosition, args.getInt("newCursorPosition"));
+                final TextAttribute textAttribute = args.getParcelable("textAttribute");
+                assertThat(textAttribute).isNotNull();
+                assertThat(textAttribute.getTextConversionSuggestions())
+                        .containsExactlyElementsIn(expectedSuggestions);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingText(CharSequence, int, TextAttribute)} fails fast
+     * once {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetComposingTextAfterUnbindInputWithTextAttribute() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingText(CharSequence text, int newCursorPosition,
+                    TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putCharSequence("text", text);
+                    args.putInt("newCursorPosition", newCursorPosition);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetComposingText(
+                    "text", 1, new TextAttribute.Builder()
+                            .setTextConversionSuggestions(Collections.singletonList("test"))
+                            .build());
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setComposingText() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setComposingText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingRegion(int, int)} works as expected.
+     */
+    @Test
+    public void testSetComposingRegion() throws Exception {
+        final int expectedStart = 3;
+        final int expectedEnd = 17;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingRegion(int start, int end) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetComposingRegion(expectedStart, expectedEnd);
+            assertTrue("setComposingRegion() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedStart, args.getInt("start"));
+                assertEquals(expectedEnd, args.getInt("end"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingRegion(int, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetComposingRegionTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingRegion(int start, int end) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetComposingRegion(1, 23);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setComposingRegion() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setComposingRegion() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that the app does not crash even if it does not implement
+     * {@link InputConnection#setComposingRegion(int, int)}, which can happen if the app was built
+     * before {@link android.os.Build.VERSION_CODES#GINGERBREAD}.
+     */
+    @Test
+    public void testSetComposingRegionFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetComposingRegion(1, 23);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#setComposingRegion() returns true even when the target app does not"
+                    + " implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingRegion} works as expected.
+     */
+    @Test
+    public void testSetComposingRegionWithTextAttribute() throws Exception {
+        final int expectedStart = 3;
+        final int expectedEnd = 17;
+        final ArrayList<String> expectedSuggestions = new ArrayList<>();
+        expectedSuggestions.add("test");
+        final TextAttribute expectedTextAttribute = new TextAttribute.Builder()
+                .setTextConversionSuggestions(expectedSuggestions).build();
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingRegion(
+                    int start, int end, TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetComposingRegion(
+                    expectedStart, expectedEnd, expectedTextAttribute);
+            assertTrue("setComposingRegion() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedStart, args.getInt("start"));
+                assertEquals(expectedEnd, args.getInt("end"));
+                final TextAttribute textAttribute = args.getParcelable("textAttribute");
+                assertThat(textAttribute).isNotNull();
+                assertThat(textAttribute.getTextConversionSuggestions())
+                        .containsExactlyElementsIn(expectedSuggestions);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setComposingRegion(int, int, TextAttribute)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetComposingRegionTextAfterUnbindInputWithTextAttribute() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setComposingRegion(int start, int end, TextAttribute textAttribute) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                    args.putParcelable("textAttribute", textAttribute);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetComposingRegion(1, 23,
+                    new TextAttribute.Builder().setTextConversionSuggestions(
+                            Collections.singletonList("test")).build());
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setComposingRegion() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setComposingRegion() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#finishComposingText()} works as expected.
+     */
+    @Test
+    public void testFinishComposingText() throws Exception {
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean finishComposingText() {
+                methodCallVerifier.onMethodCalled(bundle -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callFinishComposingText();
+            assertTrue("finishComposingText() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#finishComposingText()} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testFinishComposingTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean finishComposingText() {
+                methodCallVerifier.onMethodCalled(bundle -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // The system internally calls "finishComposingText". So wait for a while then reset
+            // the verifier before our calling "finishComposingText".
+            SystemClock.sleep(TIMEOUT);
+            methodCallVerifier.reset();
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callFinishComposingText();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#finishComposingText() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#finishComposingText() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitCompletion(CompletionInfo)} works as expected.
+     */
+    @Test
+    public void testCommitCompletion() throws Exception {
+        final CompletionInfo expectedCompletionInfo = new CompletionInfo(0x12345678, 0x87654321,
+                createTestCharSequence("testText", new Annotation("param", "text")),
+                createTestCharSequence("testLabel", new Annotation("param", "label")));
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitCompletion(CompletionInfo text) {
+                methodCallVerifier.onMethodCalled(bundle -> {
+                    bundle.putParcelable("text", text);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCommitCompletion(expectedCompletionInfo);
+            assertTrue("commitCompletion() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final CompletionInfo actualCompletionInfo = args.getParcelable("text");
+                assertNotNull(actualCompletionInfo);
+                assertEquals(expectedCompletionInfo.getId(), actualCompletionInfo.getId());
+                assertEquals(expectedCompletionInfo.getPosition(),
+                        actualCompletionInfo.getPosition());
+                assertEqualsForTestCharSequence(expectedCompletionInfo.getText(),
+                        actualCompletionInfo.getText());
+                assertEqualsForTestCharSequence(expectedCompletionInfo.getLabel(),
+                        actualCompletionInfo.getLabel());
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitCompletion(CompletionInfo)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testCommitCompletionAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitCompletion(CompletionInfo text) {
+                methodCallVerifier.onMethodCalled(bundle -> {
+                    bundle.putParcelable("text", text);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callCommitCompletion(new CompletionInfo(
+                    0x12345678, 0x87654321,
+                    createTestCharSequence("testText", new Annotation("param", "text")),
+                    createTestCharSequence("testLabel", new Annotation("param", "label"))));
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#commitCompletion() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#commitCompletion() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitCorrection(CorrectionInfo)} works as expected.
+     */
+    @Test
+    public void testCommitCorrection() throws Exception {
+        final CorrectionInfo expectedCorrectionInfo = new CorrectionInfo(0x11111111,
+                createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+                createTestCharSequence("testNewText", new Annotation("param", "newText")));
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitCorrection(CorrectionInfo correctionInfo) {
+                methodCallVerifier.onMethodCalled(bundle -> {
+                    bundle.putParcelable("correctionInfo", correctionInfo);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCommitCorrection(expectedCorrectionInfo);
+            assertTrue("commitCorrection() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final CorrectionInfo actualCorrectionInfo = args.getParcelable("correctionInfo");
+                assertNotNull(actualCorrectionInfo);
+                assertEquals(expectedCorrectionInfo.getOffset(),
+                        actualCorrectionInfo.getOffset());
+                assertEqualsForTestCharSequence(expectedCorrectionInfo.getOldText(),
+                        actualCorrectionInfo.getOldText());
+                assertEqualsForTestCharSequence(expectedCorrectionInfo.getNewText(),
+                        actualCorrectionInfo.getNewText());
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#commitCorrection(CorrectionInfo)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testCommitCorrectionAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean commitCorrection(CorrectionInfo correctionInfo) {
+                methodCallVerifier.onMethodCalled(bundle -> {
+                    bundle.putParcelable("correctionInfo", correctionInfo);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callCommitCorrection(new CorrectionInfo(0x11111111,
+                    createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+                    createTestCharSequence("testNewText", new Annotation("param", "newText"))));
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#commitCorrection() still returns true even after"
+                    + " unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#commitCorrection() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that the app does not crash even if it does not implement
+     * {@link InputConnection#commitCorrection(CorrectionInfo)}, which can happen if the app was
+     * built before {@link android.os.Build.VERSION_CODES#HONEYCOMB}.
+     */
+    @Test
+    public void testCommitCorrectionFailWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCommitCorrection(new CorrectionInfo(0x11111111,
+                    createTestCharSequence("testOldText", new Annotation("param", "oldText")),
+                    createTestCharSequence("testNewText", new Annotation("param", "newText"))));
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#commitCorrection() returns true even when the target app does not"
+                    + " implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setSelection(int, int)} works as expected.
+     */
+    @Test
+    public void testSetSelection() throws Exception {
+        final int expectedStart = 123;
+        final int expectedEnd = 456;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setSelection(int start, int end) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetSelection(expectedStart, expectedEnd);
+            assertTrue("setSelection() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedStart, args.getInt("start"));
+                assertEquals(expectedEnd, args.getInt("end"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setSelection(int, int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetSelectionTextAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setSelection(int start, int end) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("start", start);
+                    args.putInt("end", end);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetSelection(123, 456);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setSelection() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setSelection() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performEditorAction(int)} works as expected.
+     */
+    @Test
+    public void testPerformEditorAction() throws Exception {
+        final int expectedEditorAction = EditorInfo.IME_ACTION_GO;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performEditorAction(int editorAction) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("editorAction", editorAction);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callPerformEditorAction(expectedEditorAction);
+            assertTrue("performEditorAction() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedEditorAction, args.getInt("editorAction"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performEditorAction(int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testPerformEditorActionAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performEditorAction(int editorAction) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("editorAction", editorAction);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callPerformEditorAction(EditorInfo.IME_ACTION_GO);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#performEditorAction() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#performEditorAction() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performContextMenuAction(int)} works as expected.
+     */
+    @Test
+    public void testPerformContextMenuAction() throws Exception {
+        final int expectedId = android.R.id.selectAll;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performContextMenuAction(int id) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("id", id);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callPerformContextMenuAction(expectedId);
+            assertTrue("performContextMenuAction() always returns true unless RemoteException is "
+                            + "thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                assertEquals(expectedId, args.getInt("id"));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performContextMenuAction(int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testPerformContextMenuActionAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performContextMenuAction(int id) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("id", id);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callPerformEditorAction(EditorInfo.IME_ACTION_GO);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#performContextMenuAction() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#performContextMenuAction() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#beginBatchEdit()} works as expected.
+     */
+    @Test
+    public void testBeginBatchEdit() throws Exception {
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean beginBatchEdit() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callBeginBatchEdit();
+            assertTrue("beginBatchEdit() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#beginBatchEdit()} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testBeginBatchEditAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean beginBatchEdit() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callBeginBatchEdit();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#beginBatchEdit() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#beginBatchEdit() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#endBatchEdit()} works as expected.
+     */
+    @Test
+    public void testEndBatchEdit() throws Exception {
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean endBatchEdit() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callEndBatchEdit();
+            assertTrue("endBatchEdit() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#endBatchEdit()} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testEndBatchEditAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean endBatchEdit() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callEndBatchEdit();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#endBatchEdit() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#endBatchEdit() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#sendKeyEvent(KeyEvent)} works as expected.
+     */
+    @Test
+    public void testSendKeyEvent() throws Exception {
+        final KeyEvent expectedKeyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X);
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean sendKeyEvent(KeyEvent event) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("event", event);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSendKeyEvent(expectedKeyEvent);
+            assertTrue("sendKeyEvent() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final KeyEvent actualKeyEvent = args.getParcelable("event");
+                assertNotNull(actualKeyEvent);
+                assertEquals(expectedKeyEvent.getAction(), actualKeyEvent.getAction());
+                assertEquals(expectedKeyEvent.getKeyCode(), actualKeyEvent.getKeyCode());
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#sendKeyEvent(KeyEvent)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSendKeyEventAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean sendKeyEvent(KeyEvent event) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putParcelable("event", event);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSendKeyEvent(
+                    new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_X));
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#sendKeyEvent() still returns true even after unbindInput().",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#sendKeyEvent() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#clearMetaKeyStates(int)} works as expected.
+     */
+    @Test
+    public void testClearMetaKeyStates() throws Exception {
+        final int expectedStates = KeyEvent.META_ALT_MASK;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean clearMetaKeyStates(int states) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("states", states);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callClearMetaKeyStates(expectedStates);
+            assertTrue("clearMetaKeyStates() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final int actualStates = args.getInt("states");
+                assertEquals(expectedStates, actualStates);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#clearMetaKeyStates(int)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testClearMetaKeyStatesAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean clearMetaKeyStates(int states) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putInt("states", states);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callClearMetaKeyStates(KeyEvent.META_ALT_MASK);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#clearMetaKeyStates() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#clearMetaKeyStates() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#reportFullscreenMode(boolean)} is ignored as expected.
+     */
+    @Test
+    public void testReportFullscreenMode() throws Exception {
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean reportFullscreenMode(boolean enabled) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putBoolean("enabled", enabled);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callReportFullscreenMode(true);
+            assertFalse("reportFullscreenMode() always returns false on API 26+",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "IC#reportFullscreenMode() must be ignored on API 26+",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#reportFullscreenMode(boolean)} is ignored as expected even after
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testReportFullscreenModeAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean reportFullscreenMode(boolean enabled) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putBoolean("enabled", enabled);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callReportFullscreenMode(true);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertFalse("reportFullscreenMode() always returns false on API 26+",
+                    result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled("IC#reportFullscreenMode() must be ignored on "
+                    + "API 26+ even after unbindInput().", EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performSpellCheck()} works as expected.
+     */
+    @Test
+    public void testPerformSpellCheck() throws Exception {
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performSpellCheck() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callPerformSpellCheck();
+            assertTrue("performSpellCheck() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> { }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performSpellCheck()} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testPerformSpellCheckAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performSpellCheck() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callPerformSpellCheck();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#performSpellCheck() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#performSpellCheck() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that the default implementation of {@link InputConnection#performSpellCheck()}
+     * returns {@code true} without any crash even when the target app does not override it.
+     */
+    @Test
+    public void testPerformSpellCheckDefaultMethod() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callPerformSpellCheck();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#performSpellCheck() still returns true even when the target "
+                    + "application does not implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performPrivateCommand(String, Bundle)} works as expected.
+     */
+    @Test
+    public void testPerformPrivateCommand() throws Exception {
+        final String expectedAction = "myAction";
+        final Bundle expectedData = new Bundle();
+        final String expectedDataKey = "testKey";
+        final int expectedDataValue = 42;
+        expectedData.putInt(expectedDataKey, expectedDataValue);
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performPrivateCommand(String action, Bundle data) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putString("action", action);
+                    args.putBundle("data", data);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command =
+                    session.callPerformPrivateCommand(expectedAction, expectedData);
+            assertTrue("performPrivateCommand() always returns true unless RemoteException is "
+                    + "thrown", expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final String actualAction = args.getString("action");
+                final Bundle actualData = args.getBundle("data");
+                assertEquals(expectedAction, actualAction);
+                assertNotNull(actualData);
+                assertEquals(expectedData.get(expectedDataKey), actualData.getInt(expectedDataKey));
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#performPrivateCommand(String, Bundle)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testPerformPrivateCommandAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean performPrivateCommand(String action, Bundle data) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putString("action", action);
+                    args.putBundle("data", data);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callPerformPrivateCommand("myAction", null);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#performPrivateCommand() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#performPrivateCommand() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getHandler()} is ignored as expected.
+     */
+    @Test
+    public void testGetHandler() throws Exception {
+        final Handler returnedResult = null;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public Handler getHandler() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // The system internally calls "getHandler". So reset the verifier before our calling
+            // "callGetHandler".
+            methodCallVerifier.reset();
+            final ImeCommand command = session.callGetHandler();
+            assertTrue("getHandler() always returns null",
+                    expectCommand(stream, command, TIMEOUT).isNullReturnValue());
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled("IC#getHandler() must be ignored.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#getHandler()} is ignored as expected even after
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testGetHandlerAfterUnbindInput() throws Exception {
+        final Handler returnedResult = null;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public Handler getHandler() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // The system internally calls "getHandler". So reset the verifier before our calling
+            // "callGetHandler".
+            methodCallVerifier.reset();
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callGetHandler();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("getHandler() always returns null", result.isNullReturnValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "IC#getHandler() must be ignored even after unbindInput().",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that applications that do not implement {@link InputConnection#getHandler()} will not
+     * crash.  This can happen if the app was built before {@link android.os.Build.VERSION_CODES#N}.
+     */
+    @Test
+    public void testGetHandlerWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callGetHandler();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#getHandler() still returns null even when the target app does not"
+                    + " implement it.", result.isNullReturnValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#closeConnection()} is ignored as expected.
+     */
+    @Test
+    public void testCloseConnection() throws Exception {
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public void closeConnection() {
+                methodCallVerifier.onMethodCalled(args -> { });
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCloseConnection();
+            expectCommand(stream, command, TIMEOUT);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled("IC#getHandler() must be ignored.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#closeConnection()} is ignored as expected even after
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testCloseConnectionAfterUnbindInput() throws Exception {
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+        final CountDownLatch latch = new CountDownLatch(1);
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public void closeConnection() {
+                methodCallVerifier.onMethodCalled(args -> { });
+                latch.countDown();
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // The system internally calls "closeConnection". So wait for it to happen then reset
+            // the verifier before our calling "closeConnection".
+            assertTrue("closeConnection() must be called by the system.",
+                    latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+            methodCallVerifier.reset();
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callCloseConnection();
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "IC#closeConnection() must be ignored even after unbindInput().",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that applications that do not implement {@link InputConnection#closeConnection()}
+     * will not crash. This can happen if the app was built before
+     * {@link android.os.Build.VERSION_CODES#N}.
+     */
+    @Test
+    public void testCloseConnectionWithMethodMissing() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callCloseConnection();
+            expectCommand(stream, command, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setImeConsumesInput(boolean)} works as expected.
+     */
+    @Test
+    public void testSetImeConsumesInput() throws Exception {
+        final boolean expectedImeConsumesInput = true;
+        // Intentionally let the app return "false" to confirm that IME still receives "true".
+        final boolean returnedResult = false;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setImeConsumesInput(boolean imeConsumesInput) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putBoolean("imeConsumesInput", imeConsumesInput);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetImeConsumesInput(expectedImeConsumesInput);
+            assertTrue("setImeConsumesInput() always returns true unless RemoteException is thrown",
+                    expectCommand(stream, command, TIMEOUT).getReturnBooleanValue());
+            methodCallVerifier.expectCalledOnce(args -> {
+                final boolean actualImeConsumesInput = args.getBoolean("imeConsumesInput");
+                assertEquals(expectedImeConsumesInput, actualImeConsumesInput);
+            }, TIMEOUT);
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#setImeConsumesInput(boolean)} fails fast once
+     * {@link android.view.inputmethod.InputMethod#unbindInput()} is issued.
+     */
+    @Test
+    public void testSetImeConsumesInputAfterUnbindInput() throws Exception {
+        final boolean returnedResult = true;
+
+        final MethodCallVerifier methodCallVerifier = new MethodCallVerifier();
+
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public boolean setImeConsumesInput(boolean imeConsumesInput) {
+                methodCallVerifier.onMethodCalled(args -> {
+                    args.putBoolean("imeConsumesInput", imeConsumesInput);
+                });
+                return returnedResult;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            // Memorize the current InputConnection.
+            expectCommand(stream, session.memorizeCurrentInputConnection(), TIMEOUT);
+
+            // Let unbindInput happen.
+            triggerUnbindInput();
+            expectEvent(stream, event -> "unbindInput".equals(event.getEventName()), TIMEOUT);
+
+            // Now this API call on the memorized IC should fail fast.
+            final ImeCommand command = session.callSetImeConsumesInput(true);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            // CAVEAT: this behavior is a bit questionable and may change in a future version.
+            assertTrue("Currently IC#setImeConsumesInput() still returns true even after "
+                    + "unbindInput().", result.getReturnBooleanValue());
+            expectElapseTimeLessThan(result, IMMEDIATE_TIMEOUT_NANO);
+
+            // Make sure that the app does not receive the call (for a while).
+            methodCallVerifier.expectNotCalled(
+                    "Once unbindInput() happened, IC#setImeConsumesInput() fails fast.",
+                    EXPECTED_NOT_CALLED_TIMEOUT);
+        });
+    }
+
+    /**
+     * Verify that the default implementation of
+     * {@link InputConnection#setImeConsumesInput(boolean)} returns {@code true} without any crash
+     * even when the target app does not override it.
+     */
+    @Test
+    public void testSetImeConsumesInputDefaultMethod() throws Exception {
+        testMinimallyImplementedInputConnection((MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callSetImeConsumesInput(true);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+            assertTrue("IC#setImeConsumesInput() still returns true even when the target "
+                    + "application does not implement it.", result.getReturnBooleanValue());
+        });
+    }
+
+    /**
+     * Test {@link InputConnection#takeSnapshot()} is ignored as expected.
+     */
+    @Test
+    public void testTakeSnapshot() throws Exception {
+        final TextSnapshot returnedTextSnapshot = new TextSnapshot(
+                new SurroundingText("test", 4, 4, 0), -1, -1, 0);
+        final class Wrapper extends InputConnectionWrapper {
+            private Wrapper(InputConnection target) {
+                super(target, false);
+            }
+
+            @Override
+            public TextSnapshot takeSnapshot() {
+                return returnedTextSnapshot;
+            }
+        }
+
+        testInputConnection(Wrapper::new, (MockImeSession session, ImeEventStream stream) -> {
+            final ImeCommand command = session.callTakeSnapshot();
+            assertTrue("takeSnapshot() always returns null",
+                    expectCommand(stream, command, TIMEOUT).isNullReturnValue());
+        });
+    }
+
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java
new file mode 100644
index 0000000..9939e42
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputConnectionHandlerTest.java
@@ -0,0 +1,465 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Process;
+import android.os.SystemClock;
+import android.platform.test.annotations.LargeTest;
+import android.system.Os;
+import android.text.InputType;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.HandlerInputConnection;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.cts.mockime.ImeCommand;
+import com.android.cts.mockime.ImeEvent;
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests the thread-affinity in {@link InputConnection} callbacks provided by
+ * {@link InputConnection#getHandler()}.
+ *
+ * <p>TODO: Add more tests.</p>
+ */
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class InputConnectionHandlerTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    /**
+     * The value used in android.inputmethodservice.RemoteInputConnection#MAX_WAIT_TIME_MILLIS.
+     *
+     * <p>Although this is not a strictly-enforced timeout for all the Android devices, hopefully
+     * it'd be acceptable to assume that IMEs can receive result within 2 second even on slower
+     * devices.</p>
+     *
+     * <p>TODO: Consider making this as a test API.</p>
+     */
+    private static final long TIMEOUT_IN_REMOTE_INPUT_CONNECTION =
+            TimeUnit.MILLISECONDS.toMillis(2000);
+
+    private static final int TEST_VIEW_HEIGHT = 10;
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.InputConnectionHandlerTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    private static final class InputConnectionHandlingThread extends HandlerThread
+            implements AutoCloseable {
+
+        private final Handler mHandler;
+
+        InputConnectionHandlingThread() {
+            super("IC-callback");
+            start();
+            mHandler = Handler.createAsync(getLooper());
+        }
+
+        @NonNull
+        Handler getHandler() {
+            return mHandler;
+        }
+
+        @Override
+        public void close() {
+            quitSafely();
+            try {
+                join(TIMEOUT);
+            } catch (InterruptedException e) {
+                fail("Failed to stop the thread: " + e);
+            }
+        }
+    }
+
+    /**
+     * A mostly-minimum implementation of {@link View} that can be used to test custom
+     * implementations of {@link View#onCreateInputConnection(EditorInfo)}.
+     */
+    static class TestEditor extends View {
+        TestEditor(@NonNull Context context) {
+            super(context);
+            setBackgroundColor(Color.YELLOW);
+            setFocusableInTouchMode(true);
+            setFocusable(true);
+            setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, TEST_VIEW_HEIGHT));
+        }
+    }
+
+    /**
+     * Test {@link InputConnection#commitText(CharSequence, int)} respects
+     * {@link InputConnection#getHandler()}.
+     */
+    @Test
+    public void testCommitText() throws Exception {
+        try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread();
+             MockImeSession imeSession = MockImeSession.create(
+                     InstrumentationRegistry.getInstrumentation().getContext(),
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            final AtomicInteger callingThreadId = new AtomicInteger(0);
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            final class MyInputConnection extends HandlerInputConnection {
+                MyInputConnection() {
+                    super(thread.getHandler());
+                }
+
+                @Override
+                public boolean commitText(CharSequence text, int newCursorPosition) {
+                    callingThreadId.set(Os.gettid());
+                    latch.countDown();
+                    return super.commitText(text, newCursorPosition);
+                }
+            }
+
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+                // injecting our custom InputConnection implementation.
+                final TestEditor testEditor = new TestEditor(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return imeSession.isActive();
+                    }
+
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        if (imeSession.isActive()) {
+                            outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+                            outAttrs.privateImeOptions = marker;
+                            return new MyInputConnection();
+                        }
+                        return null;
+                    }
+                };
+
+                testEditor.requestFocus();
+                layout.addView(testEditor);
+                return layout;
+            });
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Wait until "onStartInput" gets called for the EditText.
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            final ImeCommand command = imeSession.callCommitText("", 1);
+            expectCommand(stream, command, TIMEOUT);
+
+            assertTrue("commitText() must be called", latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+            assertEquals("commitText() must happen on the handler thread",
+                    thread.getThreadId(), callingThreadId.get());
+        }
+    }
+
+    /**
+     * Test {@link InputConnection#reportFullscreenMode(boolean)} respects
+     * {@link InputConnection#getHandler()}.
+     */
+    @Test
+    public void testReportFullscreenMode() throws Exception {
+        try (InputConnectionHandlingThread thread = new InputConnectionHandlingThread();
+             MockImeSession imeSession = MockImeSession.create(
+                     InstrumentationRegistry.getInstrumentation().getContext(),
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder().setFullscreenModePolicy(
+                             ImeSettings.FullscreenModePolicy.FORCE_FULLSCREEN))) {
+
+            final AtomicInteger callingThreadId = new AtomicInteger(0);
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            final class MyInputConnection extends HandlerInputConnection {
+                MyInputConnection() {
+                    super(thread.getHandler());
+                }
+
+                @Override
+                public boolean reportFullscreenMode(boolean enabled) {
+                    callingThreadId.set(Os.gettid());
+                    latch.countDown();
+                    return true;
+                }
+            }
+
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+
+            final AtomicReference<View> testEditorViewRef = new AtomicReference<>();
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+                // injecting our custom InputConnection implementation.
+                final TestEditor testEditor = new TestEditor(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return imeSession.isActive();
+                    }
+
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        if (imeSession.isActive()) {
+                            outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+                            outAttrs.privateImeOptions = marker;
+                            return new MyInputConnection();
+                        }
+                        return null;
+                    }
+                };
+
+                testEditor.requestFocus();
+                testEditorViewRef.set(testEditor);
+                layout.addView(testEditor);
+                return layout;
+            });
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            assertFalse("InputMethodManager#isFullscreenMode() must return false",
+                    getOnMainSync(() -> InstrumentationRegistry.getInstrumentation().getContext()
+                            .getSystemService(InputMethodManager.class).isFullscreenMode()));
+
+            // In order to have an IME be shown in the fullscreen mode,
+            // SOFT_INPUT_STATE_ALWAYS_VISIBLE is insufficient.  An explicit API call is necessary.
+            runOnMainSync(() -> {
+                final View editor = testEditorViewRef.get();
+                editor.getContext().getSystemService(InputMethodManager.class)
+                        .showSoftInput(editor, 0);
+            });
+
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+
+            assertTrue("reportFullscreenMode() must be called",
+                    latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+            assertEquals("reportFullscreenMode() must happen on the handler thread",
+                    thread.getThreadId(), callingThreadId.get());
+
+            assertTrue("InputMethodManager#isFullscreenMode() must return true",
+                    getOnMainSync(() -> InstrumentationRegistry.getInstrumentation().getContext()
+                            .getSystemService(InputMethodManager.class).isFullscreenMode()));
+        }
+    }
+
+    /**
+     * A holder of {@link Handler} that is bound to a background {@link Looper} where
+     * {@link Throwable} thrown from tasks running there will be just ignored instead of triggering
+     * process crashes.
+     */
+    private static final class ErrorSwallowingHandlerThread implements AutoCloseable {
+        @NonNull
+        private final Handler mHandler;
+
+        @NonNull
+        Handler getHandler() {
+            return mHandler;
+        }
+
+        @NonNull
+        static ErrorSwallowingHandlerThread create() {
+            final CountDownLatch latch = new CountDownLatch(1);
+            final AtomicReference<Looper> mLooperRef = new AtomicReference<>();
+            new Thread(() -> {
+                Looper.prepare();
+                mLooperRef.set(Looper.myLooper());
+                latch.countDown();
+
+                while (true) {
+                    try {
+                        Looper.loop();
+                        return;
+                    } catch (Throwable ignore) {
+                    }
+                }
+            }).start();
+
+            try {
+                assertTrue(latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+            } catch (InterruptedException e) {
+                Thread.currentThread().interrupt();
+                fail("Failed to create a Handler thread");
+            }
+
+            final Handler handler = Handler.createAsync(mLooperRef.get());
+            return new ErrorSwallowingHandlerThread(handler);
+        }
+
+        private ErrorSwallowingHandlerThread(@NonNull Handler handler) {
+            mHandler = handler;
+        }
+
+        @Override
+        public void close() {
+            mHandler.getLooper().quitSafely();
+            try {
+                mHandler.getLooper().getThread().join(TIMEOUT);
+            } catch (InterruptedException e) {
+                fail("Failed to terminate the thread");
+            }
+        }
+    }
+
+    /**
+     * Ensures that {@code event}'s elapse time is less than the given threshold.
+     *
+     * @param event {@link ImeEvent} to be tested.
+     * @param elapseThresholdInMilliSecond threshold in milli sec.
+     */
+    private static void expectElapseTimeLessThan(@NonNull ImeEvent event,
+            long elapseThresholdInMilliSecond) {
+        final long elapseTimeInMilli = TimeUnit.NANOSECONDS.toMillis(
+                event.getExitTimestamp() - event.getEnterTimestamp());
+        if (elapseTimeInMilli > elapseThresholdInMilliSecond) {
+            fail(event.getEventName() + " took " + elapseTimeInMilli + " msec,"
+                    + " which must be less than " + elapseThresholdInMilliSecond + " msec.");
+        }
+    }
+
+    /**
+     * Test {@link InputConnection#getSurroundingText(int, int, int)} that throws an exception.
+     */
+    @Test
+    public void testExceptionFromGetSurroundingText() throws Exception {
+        try (ErrorSwallowingHandlerThread handlerThread = ErrorSwallowingHandlerThread.create();
+             MockImeSession imeSession = MockImeSession.create(
+                     InstrumentationRegistry.getInstrumentation().getContext(),
+                     InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                     new ImeSettings.Builder())) {
+
+            final CountDownLatch latch = new CountDownLatch(1);
+
+            final class MyInputConnection extends HandlerInputConnection {
+                MyInputConnection() {
+                    super(handlerThread.getHandler());
+                }
+
+                @Override
+                public SurroundingText getSurroundingText(int beforeLength, int afterLength,
+                        int flags) {
+                    latch.countDown();
+                    throw new RuntimeException("Exception!");
+                }
+
+            }
+
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                // Just to be conservative, we explicitly check MockImeSession#isActive() here when
+                // injecting our custom InputConnection implementation.
+                final TestEditor testEditor = new TestEditor(activity) {
+                    @Override
+                    public boolean onCheckIsTextEditor() {
+                        return imeSession.isActive();
+                    }
+
+                    @Override
+                    public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                        if (imeSession.isActive()) {
+                            outAttrs.inputType = InputType.TYPE_CLASS_TEXT;
+                            outAttrs.privateImeOptions = marker;
+                            return new MyInputConnection();
+                        }
+                        return null;
+                    }
+                };
+
+                testEditor.requestFocus();
+                layout.addView(testEditor);
+                return layout;
+            });
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Wait until "onStartInput" gets called for the EditText.
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            final ImeCommand command = imeSession.callGetSurroundingText(1, 1, 0);
+            final ImeEvent result = expectCommand(stream, command, TIMEOUT);
+
+            assertTrue("IC#getSurroundingText() must be called",
+                    latch.await(TIMEOUT, TimeUnit.MILLISECONDS));
+
+            assertTrue("Exceptions from IC#getSurroundingText() must be interpreted as null.",
+                    result.isNullReturnValue());
+            expectElapseTimeLessThan(result, TIMEOUT_IN_REMOTE_INPUT_CONNECTION);
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
index 5eaa32e..9b1c836 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodManagerTest.java
@@ -207,7 +207,7 @@
                 PackageManager.FEATURE_INPUT_METHODS));
         enableImes(MOCK_IME_ID, HIDDEN_FROM_PICKER_IME_ID);
 
-        final TestActivity testActivity = TestActivity.startSync(activity -> {
+        TestActivity.startSync(activity -> {
             final View view = new View(activity);
             view.setLayoutParams(new LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
@@ -222,9 +222,8 @@
 
         // Test InputMethodManager#showInputMethodPicker() works as expected.
         mImManager.showInputMethodPicker();
-        waitOnMainUntil(() -> mImManager.isInputMethodPickerShown()
-                        && !testActivity.hasWindowFocus(), TIMEOUT,
-                "InputMethod picker should be shown and test activity lost focus");
+        waitOnMainUntil(() -> mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "InputMethod picker should be shown");
         final UiDevice uiDevice =
                 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         assertThat(uiDevice.wait(Until.hasObject(By.text(MOCK_IME_LABEL)), TIMEOUT)).isTrue();
@@ -235,8 +234,6 @@
                 new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
         waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
                 "InputMethod picker should be closed");
-        waitOnMainUntil(() -> testActivity.hasWindowFocus(), TIMEOUT,
-                "Activity should be focused after picker dismissed");
     }
 
     private void enableImes(String... ids) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java
new file mode 100644
index 0000000..3064c30
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodPickerTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
+import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.SecurityTest;
+import android.server.wm.ActivityManagerTestBase;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.LinearLayout;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.MediumTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class InputMethodPickerTest extends ActivityManagerTestBase {
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    private InputMethodManager mImManager;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mImManager = mContext.getSystemService(InputMethodManager.class);
+
+        closeSystemDialogsAndWait();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        closeSystemDialogsAndWait();
+    }
+
+    @AppModeFull(reason = "Instant apps cannot rely on ACTION_CLOSE_SYSTEM_DIALOGS")
+    @SecurityTest(minPatchLevel = "unknown")
+    @Test
+    public void testInputMethodPicker_hidesUntrustedOverlays() throws Exception {
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS));
+        TestActivity testActivity = TestActivity.startSync(activity -> {
+            final View view = new View(activity);
+            view.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
+            return view;
+        });
+
+        // Test setup: Show overlay and verify that it worked.
+        getInstrumentation().runOnMainSync(() -> {
+            testActivity.showOverlayWindow();
+        });
+        mWmState.waitAndAssertWindowSurfaceShown(TestActivity.OVERLAY_WINDOW_NAME, true);
+
+        // Test setup: Show the IME picker and verify that it worked.
+        getInstrumentation().runOnMainSync(() -> {
+            mImManager.showInputMethodPicker();
+        });
+        waitOnMainUntil(() -> mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "Test setup failed: InputMethod picker should be shown");
+
+        // Actual Test: Make sure the IME picker hides app overlays.
+        mWmState.waitAndAssertWindowSurfaceShown(TestActivity.OVERLAY_WINDOW_NAME, false);
+    }
+
+    private void closeSystemDialogsAndWait() throws Exception {
+        mContext.sendBroadcast(
+                new Intent(ACTION_CLOSE_SYSTEM_DIALOGS).setFlags(FLAG_RECEIVER_FOREGROUND));
+        waitOnMainUntil(() -> !mImManager.isInputMethodPickerShown(), TIMEOUT,
+                "Test assertion failed: InputMethod picker should be closed but isn't");
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 6eeb1da..cfffd9d 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
 import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
@@ -31,6 +33,7 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectNoImeCrash;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.verificationMatcher;
 
@@ -57,6 +60,7 @@
 import android.view.inputmethod.InputConnectionWrapper;
 import android.view.inputmethod.InputMethodManager;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.SimulatedVirtualDisplaySession;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.TestActivity2;
 import android.view.inputmethod.cts.util.TestUtils;
@@ -98,6 +102,8 @@
 public class InputMethodServiceTest extends EndToEndImeTestBase {
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(20);
     private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
+    private static final long ACTIVITY_LAUNCH_INTERVAL = 500;  // msec
+
 
     private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
     // 1.2 is an arbitrary value.
@@ -126,11 +132,15 @@
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
-    private TestActivity createTestActivity(final int windowFlags) {
+    private TestActivity createTestActivity(int windowFlags) {
         return TestActivity.startSync(activity -> createLayout(windowFlags, activity));
     }
 
-    private TestActivity createTestActivity2(final int windowFlags) {
+    private TestActivity createTestActivity(int windowFlags, int displayId) throws Exception {
+        return TestActivity.startSync(displayId, activity -> createLayout(windowFlags, activity));
+    }
+
+    private TestActivity createTestActivity2(int windowFlags) {
         return TestActivity2.startSync(activity -> createLayout(windowFlags, activity));
     }
 
@@ -502,6 +512,7 @@
         try (MockImeSession imeSession = MockImeSession.create(
                 mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
                 new ImeSettings.Builder().setVerifyUiContextApisInOnCreate(true))) {
+            ensureImeRunning();
             final ImeEventStream stream = imeSession.openEventStream();
 
             // Verify if getDisplay doesn't throw exception before InputMethodService's
@@ -699,6 +710,7 @@
         try (MockImeSession imeSession = MockImeSession.create(
                 mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
                 new ImeSettings.Builder().setVerifyUiContextApisInOnCreate(true))) {
+            ensureImeRunning();
             final ImeEventStream stream = imeSession.openEventStream();
 
             // Verify if InputMethodService#isUiContext returns true in #onCreate
@@ -730,6 +742,31 @@
         }
     }
 
+    @Test
+    public void testNoExceptionWhenSwitchingDisplaysWithImeReCreate() throws Exception {
+        try (SimulatedVirtualDisplaySession displaySession = SimulatedVirtualDisplaySession.create(
+                mInstrumentation.getContext(), 800, 600, 240, DISPLAY_IME_POLICY_LOCAL);
+                     MockImeSession imeSession = MockImeSession.create(
+                             mInstrumentation.getContext(), mInstrumentation.getUiAutomation(),
+                             new ImeSettings.Builder())) {
+            // Launch activity repeatedly with re-create / showing IME on different displays
+            for (int i = 0; i < 10; i++) {
+                int displayId = (i % 2 == 0) ? displaySession.getDisplayId() : DEFAULT_DISPLAY;
+                createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE, displayId);
+                SystemClock.sleep(ACTIVITY_LAUNCH_INTERVAL);
+            }
+            // Verify no crash and onCreate / onDestroy keeps paired from MockIme event stream
+            expectNoImeCrash(imeSession, TIMEOUT);
+        }
+    }
+
+    /** Explicitly start-up the IME process if it would have been prevented. */
+    protected void ensureImeRunning() {
+        if (isPreventImeStartup()) {
+            createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+        }
+    }
+
     /** Test case for committing and setting composing region after cursor. */
     private static UpdateSelectionTest getCommitAndSetComposingRegionTest(
             long timeout, String makerPrefix) throws Exception {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
index 4eb6573..24a0e09 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodStartInputLifecycleTest.java
@@ -27,6 +27,8 @@
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
@@ -34,14 +36,23 @@
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.graphics.Color;
 import android.inputmethodservice.InputMethodService;
 import android.os.IBinder;
 import android.os.Process;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.Selection;
 import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.BaseInputConnection;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextSnapshot;
 import android.view.inputmethod.cts.util.DisableScreenDozeRule;
 import android.view.inputmethod.cts.util.EndToEndImeTestBase;
 import android.view.inputmethod.cts.util.RequireImeCompatFlagRule;
@@ -52,6 +63,7 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -70,6 +82,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiFunction;
 import java.util.function.Predicate;
 
 @MediumTest
@@ -84,6 +97,14 @@
             FINISH_INPUT_NO_FALLBACK_CONNECTION, true);
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.FocusHandlingTest";
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
 
     @AppModeFull(reason = "KeyguardManager is not accessible from instant apps")
     @Test
@@ -99,8 +120,7 @@
                 context, instrumentation.getUiAutomation(), new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
 
-            final String marker = InputMethodManagerTest.class.getName() + "/"
-                    + SystemClock.elapsedRealtimeNanos();
+            final String marker = getTestMarker();
             final AtomicInteger screenStateCallbackRef = new AtomicInteger(-1);
             TestActivity.startSync(activity -> {
                 final LinearLayout layout = new LinearLayout(activity);
@@ -187,8 +207,7 @@
                 new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
 
-            final String marker = InputMethodStartInputLifecycleTest.class.getName() + "/"
-                    + SystemClock.elapsedRealtimeNanos();
+            final String marker = getTestMarker();
             final EditText editText = launchTestActivity(marker);
             TestUtils.runOnMainSync(() -> editText.requestFocus());
 
@@ -243,6 +262,265 @@
         return editTextRef.get();
     }
 
+    /**
+     * A mostly-minimum implementation of {@link View} that can be used to test custom
+     * implementations of {@link View#onCreateInputConnection(EditorInfo)}.
+     */
+    static class TestEditor extends View {
+        TestEditor(@NonNull Context context) {
+            super(context);
+            setBackgroundColor(Color.YELLOW);
+            setFocusableInTouchMode(true);
+            setFocusable(true);
+            setLayoutParams(new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT, 10 /* height */));
+        }
+    }
+
+    private abstract static class TestInputConnection extends BaseInputConnection {
+        @NonNull
+        private final Editable mEditable;
+
+        TestInputConnection(@NonNull View view, @NonNull Editable editable) {
+            super(view, true /* fullEditor */);
+            mEditable = editable;
+        }
+
+        @NonNull
+        @Override
+        public final Editable getEditable() {
+            return mEditable;
+        }
+    }
+
+    @Test
+    public void testInvalidateInput() throws Exception {
+        // If IC#takeSnapshot() returns true, it should work, even if IC#{begin,end}BatchEdit()
+        // always return false.
+        expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+            @Override
+            public boolean beginBatchEdit() {
+                return false;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                return false;
+            }
+        });
+
+        // Of course IMM#invalidateInput() should just work for ICs that support
+        // {begin,end}BatchEdit().
+        expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+            private int mBatchEditCount = 0;
+            @Override
+            public boolean beginBatchEdit() {
+                ++mBatchEditCount;
+                return true;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                if (mBatchEditCount <= 0) {
+                    return false;
+                }
+                --mBatchEditCount;
+                return mBatchEditCount > 0;
+            }
+        });
+
+        // If IC#takeSnapshot() returns false, then fall back to IMM#restartInput()
+        expectFallbackInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+            @Override
+            public boolean beginBatchEdit() {
+                return false;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                return false;
+            }
+            @Override
+            public TextSnapshot takeSnapshot() {
+                return null;
+            }
+        });
+
+        // Bug 209958658 should not prevent the system from using the native invalidateInput().
+        expectNativeInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+            private int mBatchEditCount = 0;
+
+            @Override
+            public boolean beginBatchEdit() {
+                ++mBatchEditCount;
+                return true;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                if (mBatchEditCount <= 0) {
+                    return false;
+                }
+                --mBatchEditCount;
+                // This violates the spec. See Bug 209958658 for instance.
+                return true;
+            }
+        });
+
+        // Even if IC#endBatchEdit() never returns false, the system should be able to fall back
+        // to IMM#restartInput().  This is a regression test for Bug 208941904.
+        expectFallbackInvalidateInput((view, editable) -> new TestInputConnection(view, editable) {
+            @Override
+            public boolean beginBatchEdit() {
+                return true;
+            }
+            @Override
+            public boolean endBatchEdit() {
+                return true;
+            }
+        });
+    }
+
+    private void expectNativeInvalidateInput(
+            BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+        testInvalidateInputMain(true, inputConnectionProvider);
+    }
+
+    private void expectFallbackInvalidateInput(
+            BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+        testInvalidateInputMain(false, inputConnectionProvider);
+    }
+
+    private void testInvalidateInputMain(boolean expectNativeInvalidateInput,
+            BiFunction<View, Editable, InputConnection> inputConnectionProvider) throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        try (MockImeSession imeSession = MockImeSession.create(
+                instrumentation.getContext(),
+                instrumentation.getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final int initialSelStart = 3;
+            final int initialSelEnd = 7;
+            final int initialCapsMode = TextUtils.CAP_MODE_SENTENCES;
+
+            final AtomicInteger onCreateConnectionCount = new AtomicInteger(0);
+            class MyTestEditor extends TestEditor {
+                final Editable mEditable;
+
+                MyTestEditor(Context context, @NonNull Editable editable) {
+                    super(context);
+                    mEditable = editable;
+                }
+
+                @Override
+                public boolean onCheckIsTextEditor() {
+                    return true;
+                }
+
+                @Override
+                public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+                    onCreateConnectionCount.incrementAndGet();
+                    outAttrs.inputType =
+                            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_CAP_SENTENCES;
+                    outAttrs.initialSelStart = Selection.getSelectionStart(mEditable);
+                    outAttrs.initialSelEnd = Selection.getSelectionEnd(mEditable);
+                    outAttrs.initialCapsMode = initialCapsMode;
+                    outAttrs.privateImeOptions = marker;
+                    outAttrs.setInitialSurroundingText(mEditable);
+                    return inputConnectionProvider.apply(this, mEditable);
+                }
+            }
+
+            final AtomicReference<MyTestEditor> myEditorRef = new AtomicReference<>();
+            TestActivity.startSync(activity -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final Editable editable =
+                        Editable.Factory.getInstance().newEditable("0123456789");
+                Selection.setSelection(editable, initialSelStart, initialSelEnd);
+
+                final MyTestEditor editor = new MyTestEditor(activity, editable);
+                editor.requestFocus();
+                myEditorRef.set(editor);
+
+                layout.addView(editor);
+                return layout;
+            });
+            final MyTestEditor myEditor = myEditorRef.get();
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            {
+                final ImeEvent startInputEvent =
+                        expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+                final EditorInfo editorInfo =
+                        startInputEvent.getArguments().getParcelable("editorInfo");
+                assertThat(editorInfo).isNotNull();
+                assertThat(editorInfo.initialSelStart).isEqualTo(initialSelStart);
+                assertThat(editorInfo.initialSelEnd).isEqualTo(initialSelEnd);
+                assertThat(editorInfo.getInitialSelectedText(0).toString()).isEqualTo("3456");
+            }
+
+            stream.skipAll();
+            final ImeEventStream forkedStream = stream.copy();
+
+            final int prevOnCreateInputConnectionCount = onCreateConnectionCount.get();
+
+            final int newSelStart = 1;
+            final int newSelEnd = 3;
+            TestUtils.runOnMainSync(() -> {
+                Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+                final InputMethodManager imm = myEditor.getContext().getSystemService(
+                        InputMethodManager.class);
+                imm.invalidateInput(myEditor);
+            });
+
+            // Verify that InputMethodService#onStartInput() is triggered as if IMM#restartInput()
+            // was called.
+            {
+                final ImeEvent startInputEvent =
+                        expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+                final boolean restarting = startInputEvent.getArguments().getBoolean("restarting");
+                assertThat(restarting).isTrue();
+                final EditorInfo editorInfo =
+                        startInputEvent.getArguments().getParcelable("editorInfo");
+                assertThat(editorInfo).isNotNull();
+                assertThat(editorInfo.initialSelStart).isEqualTo(newSelStart);
+                assertThat(editorInfo.initialSelEnd).isEqualTo(newSelEnd);
+                assertThat(editorInfo.getInitialSelectedText(0).toString()).isEqualTo("12");
+            }
+
+            if (expectNativeInvalidateInput) {
+                // If InputMethodManager#interruptInput() is expected to be natively supported,
+                // additional View#onCreateInputConnection() must not happen.
+                assertThat(onCreateConnectionCount.get()).isEqualTo(
+                        prevOnCreateInputConnectionCount);
+            } else {
+                // InputMethodManager#interruptInput() is expected to be falling back into
+                // InputMethodManager#restartInput(), which triggers View#onCreateInputConnection()
+                // as a consequence.
+                assertThat(onCreateConnectionCount.get()).isGreaterThan(
+                        prevOnCreateInputConnectionCount);
+            }
+
+            // For historical reasons, InputMethodService#onFinishInput() will not be triggered when
+            // restarting an input connection.
+            assertThat(forkedStream.findFirst(onFinishInputMatcher()).isPresent()).isFalse();
+
+            // Make sure that InputMethodManager#updateSelection() will be ignored when there is
+            // no change from the last call of InputMethodManager#interruptInput().
+            TestUtils.runOnMainSync(() -> {
+                Selection.setSelection(myEditor.mEditable, newSelStart, newSelEnd);
+                final InputMethodManager imm = myEditor.getContext().getSystemService(
+                        InputMethodManager.class);
+                imm.updateSelection(myEditor, newSelStart, newSelEnd, -1, -1);
+            });
+
+            notExpectEvent(stream, event -> "onUpdateSelection".equals(event.getEventName()),
+                    NOT_EXPECT_TIMEOUT);
+        }
+    }
+
     private static Predicate<ImeEvent> onFinishInputMatcher() {
         return event -> TextUtils.equals("onFinishInput", event.getEventName());
     }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 673c87a..65a0c30 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -45,6 +45,7 @@
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
 import android.graphics.Color;
+import android.graphics.Insets;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
@@ -55,6 +56,7 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
+import android.view.WindowInsets;
 import android.view.WindowInsetsController;
 import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
@@ -76,18 +78,25 @@
 import androidx.test.filters.MediumTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.Until;
 
+import com.android.compatibility.common.util.FeatureUtil;
 import com.android.cts.mockime.ImeEvent;
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeLayoutInfo;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
 
+import org.junit.Assume;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 import java.util.Map;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Predicate;
@@ -358,21 +367,16 @@
     }
 
     @Test
-    public void testImeVisibilityWhenDismisingDialogWithImeFocused() throws Exception {
+    public void testImeVisibilityWhenDismissingDialogWithImeFocused() throws Exception {
         final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
-        final InputMethodManager imm = instrumentation.getTargetContext().getSystemService(
-                InputMethodManager.class);
         try (MockImeSession imeSession = MockImeSession.create(
-                InstrumentationRegistry.getInstrumentation().getContext(),
-                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                instrumentation.getContext(),
+                instrumentation.getUiAutomation(),
                 new ImeSettings.Builder())) {
             final ImeEventStream stream = imeSession.openEventStream();
 
             // Launch a simple test activity
-            final TestActivity testActivity = TestActivity.startSync(activity -> {
-                final LinearLayout layout = new LinearLayout(activity);
-                return layout;
-            });
+            final TestActivity testActivity = TestActivity.startSync(LinearLayout::new);
 
             // Launch a dialog
             final String marker = getTestMarker();
@@ -427,11 +431,14 @@
             expectImeInvisible(TIMEOUT);
 
             // Expect fallback input connection started and keyboard invisible after activity
-            // focused.
-            final ImeEvent onStart = expectEvent(stream,
-                    event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
-            assertTrue(onStart.getEnterState().hasFallbackInputConnection());
-            TestUtils.waitOnMainUntil(() -> testActivity.hasWindowFocus(), TIMEOUT);
+            // focused unless avoidable keyboard startup is desired,
+            // in which case, no fallback will be started.
+            if (!isPreventImeStartup()) {
+                final ImeEvent onStart = expectEvent(stream,
+                        event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+                assertTrue(onStart.getEnterState().hasFallbackInputConnection());
+            }
+            TestUtils.waitOnMainUntil(testActivity::hasWindowFocus, TIMEOUT);
             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                     View.GONE, TIMEOUT);
             expectImeInvisible(TIMEOUT);
@@ -560,12 +567,16 @@
     @AppModeFull
     @Test
     public void testImeVisibilityWhenImeTransitionBetweenActivities_Full() throws Exception {
+        // TODO(b/210041952): Temporary disable testing on TV platform until realize how to address.
+        Assume.assumeFalse(FeatureUtil.isTV());
         runImeVisibilityWhenImeTransitionBetweenActivities(false /* instant */);
     }
 
     @AppModeInstant
     @Test
     public void testImeVisibilityWhenImeTransitionBetweenActivities_Instant() throws Exception {
+        // TODO(b/210041952): Temporary disable testing on TV platform until realize how to address.
+        Assume.assumeFalse(FeatureUtil.isTV());
         runImeVisibilityWhenImeTransitionBetweenActivities(true /* instant */);
     }
 
@@ -718,20 +729,33 @@
                     View.VISIBLE, TIMEOUT);
             expectImeVisible(TIMEOUT);
 
-            // Launcher another test activity from another process with popup dialog.
+            // Launch another test activity from another process with popup dialog.
             MockTestActivityUtil.launchSync(instant, TIMEOUT,
                     Map.of(MockTestActivityUtil.EXTRA_KEY_SHOW_DIALOG, "true"));
+            BySelector dialogSelector = By.clazz(AlertDialog.class).depth(0);
+            UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+            assertNotNull(uiDevice.wait(Until.hasObject(dialogSelector), TIMEOUT));
+
             // Dismiss dialog and back to original test activity
             MockTestActivityUtil.sendBroadcastAction(MockTestActivityUtil.EXTRA_DISMISS_DIALOG);
 
+            final CountDownLatch imeVisibilityUpdateLatch = new CountDownLatch(1);
+            AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
+            TestUtils.runOnMainSync(
+                    () -> testActivity.getWindow().getDecorView().setOnApplyWindowInsetsListener(
+                            (v, insets) -> {
+                                if (insets.getInsets(WindowInsets.Type.ime()) != Insets.NONE) {
+                                    imeInsetsVisible.set(insets.isVisible(WindowInsets.Type.ime()));
+                                    imeVisibilityUpdateLatch.countDown();
+                                }
+                                return v.onApplyWindowInsets(insets);
+                            }));
             // Verify keyboard visibility should aligned with IME insets visibility.
             TestUtils.waitOnMainUntil(
                     () -> testActivity.getWindow().getDecorView().getVisibility() == VISIBLE
                             && testActivity.getWindow().getDecorView().hasWindowFocus(), TIMEOUT);
-
-            AtomicReference<Boolean> imeInsetsVisible = new AtomicReference<>();
-            TestUtils.runOnMainSync(() ->
-                    imeInsetsVisible.set(editTextRef.get().getRootWindowInsets().isVisible(ime())));
+            assertTrue("Waiting for onApplyWindowInsets timed out",
+                    imeVisibilityUpdateLatch.await(5, TimeUnit.SECONDS));
 
             if (imeInsetsVisible.get()) {
                 expectImeVisible(TIMEOUT);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
index 162c428..7470047 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/NavigationBarColorTest.java
@@ -17,6 +17,7 @@
 package android.view.inputmethod.cts;
 
 import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -48,6 +49,7 @@
 import android.view.inputmethod.cts.util.NavigationBarInfo;
 import android.view.inputmethod.cts.util.TestActivity;
 import android.view.inputmethod.cts.util.UnlockScreenRule;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -58,7 +60,6 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.ImeAwareEditText;
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeLayoutInfo;
 import com.android.cts.mockime.ImeSettings;
@@ -140,12 +141,12 @@
                 case DIMMING_DIALOG_ABOVE_IME: {
                     final LinearLayout layout = new LinearLayout(activity);
                     layout.setOrientation(LinearLayout.VERTICAL);
-                    final ImeAwareEditText editText = new ImeAwareEditText(activity);
+                    final EditText editText = new EditText(activity);
                     editText.setPrivateImeOptions(TEST_MARKER);
                     editText.setHint("editText");
                     editText.requestFocus();
-                    editText.scheduleShowSoftInput();
                     layout.addView(editText);
+                    activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
                     contentView = layout;
                     break;
                 }
@@ -193,17 +194,17 @@
             }
             case DIMMING_DIALOG_BEHIND_IME: {
                 final AlertDialog alertDialog = getOnMainSync(() -> {
-                    final ImeAwareEditText editText = new ImeAwareEditText(activity);
+                    final EditText editText = new EditText(activity);
                     editText.setPrivateImeOptions(TEST_MARKER);
                     editText.setHint("editText");
                     editText.requestFocus();
-                    editText.scheduleShowSoftInput();
                     final AlertDialog dialog = new AlertDialog.Builder(activity)
                             .setView(editText)
                             .create();
                     dialog.getWindow().setFlags(FLAG_DIM_BEHIND,
                             FLAG_DIM_BEHIND | FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
                     dialog.show();
+                    activity.getWindow().getDecorView().getWindowInsetsController().show(ime());
                     return dialog;
                 });
                 // Note: Dialog#dismiss() is a thread safe method so we don't need to call this from
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
new file mode 100644
index 0000000..3ee8e74
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/StylusHandwritingTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+import android.util.Pair;
+import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.cts.util.EndToEndImeTestBase;
+import android.view.inputmethod.cts.util.TestActivity;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.cts.mockime.ImeEventStream;
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.MockImeSession;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * IMF and end-to-end Stylus handwriting tests.
+ */
+public class StylusHandwritingTest extends EndToEndImeTestBase {
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final long NOT_EXPECT_TIMEOUT = TimeUnit.SECONDS.toMillis(1);
+    private static final String TEST_MARKER_PREFIX =
+            "android.view.inputmethod.cts.StylusHandwritingTest";
+
+    @Test
+    public void testHandwritingStartAndFinish() throws Exception {
+        final InputMethodManager imm = InstrumentationRegistry.getInstrumentation()
+                .getTargetContext().getSystemService(InputMethodManager.class);
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getInstrumentation().getContext(),
+                InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartInputView", marker),
+                    NOT_EXPECT_TIMEOUT);
+            imm.startStylusHandwriting(editText);
+            // keyboard shouldn't show up.
+            notExpectEvent(
+                    stream,
+                    editorMatcher("onStartInputView", marker),
+                    NOT_EXPECT_TIMEOUT);
+
+            // Handwriting should start
+            expectEvent(
+                    stream,
+                    event -> "onStartStylusHandwriting".equals(event.getEventName()),
+                    TIMEOUT);
+
+            // Verify Stylus Handwriting window is shown
+            assertTrue(expectCommand(
+                    stream, imeSession.callGetStylusHandwritingWindowVisibility(), TIMEOUT)
+                            .getReturnBooleanValue());
+
+            // Verify calling finishStylusHandwriting() calls onFinishStylusHandwriting().
+            imeSession.callFinishStylusHandwriting();
+            expectEvent(
+                    stream,
+                    event -> "onFinishStylusHandwriting".equals(event.getEventName()),
+                    TIMEOUT);
+        }
+    }
+
+    private EditText launchTestActivity(@NonNull String marker) {
+        return launchTestActivity(marker, getTestMarker()).first;
+    }
+
+    private static String getTestMarker() {
+        return TEST_MARKER_PREFIX + "/"  + SystemClock.elapsedRealtimeNanos();
+    }
+
+    private Pair<EditText, EditText> launchTestActivity(@NonNull String focusedMarker,
+            @NonNull String nonFocusedMarker) {
+        final AtomicReference<EditText> focusedEditTextRef = new AtomicReference<>();
+        final AtomicReference<EditText> nonFocusedEditTextRef = new AtomicReference<>();
+        TestActivity.startSync(activity -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText focusedEditText = new EditText(activity);
+            focusedEditText.setHint("focused editText");
+            focusedEditText.setPrivateImeOptions(focusedMarker);
+            focusedEditText.requestFocus();
+            focusedEditTextRef.set(focusedEditText);
+            layout.addView(focusedEditText);
+
+            final EditText nonFocusedEditText = new EditText(activity);
+            nonFocusedEditText.setPrivateImeOptions(nonFocusedMarker);
+            nonFocusedEditText.setHint("target editText");
+            nonFocusedEditTextRef.set(nonFocusedEditText);
+            layout.addView(nonFocusedEditText);
+            return layout;
+        });
+        return new Pair<>(focusedEditTextRef.get(), nonFocusedEditTextRef.get());
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.java
new file mode 100644
index 0000000..3615e1f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/TextAttributeTest.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 android.view.inputmethod.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.os.PersistableBundle;
+import android.view.inputmethod.TextAttribute;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class TextAttributeTest {
+    private static final String SUGGESTION = "suggestion";
+    private static final String EXTRAS_KEY = "extras_key";
+    private static final String EXTRAS_VALUE = "extras_value";
+
+    private static final List<String> SUGGESTIONS = Collections.singletonList(SUGGESTION);
+    private static final PersistableBundle EXTRA_BUNDLE;
+    static {
+        final PersistableBundle bundle = new PersistableBundle();
+        bundle.putString(EXTRAS_KEY, EXTRAS_VALUE);
+        EXTRA_BUNDLE = bundle;
+    }
+
+    @Test
+    public void testTextAttribute() {
+        final TextAttribute textAttribute = new TextAttribute.Builder()
+                .setTextConversionSuggestions(SUGGESTIONS)
+                .setExtras(EXTRA_BUNDLE)
+                .build();
+        assertTextAttribute(textAttribute);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        final TextAttribute textAttribute = new TextAttribute.Builder()
+                .setTextConversionSuggestions(SUGGESTIONS)
+                .setExtras(EXTRA_BUNDLE)
+                .build();
+
+        assertTextAttribute(cloneViaParcel(textAttribute));
+    }
+
+    private static void assertTextAttribute(TextAttribute info) {
+        assertThat(info.getTextConversionSuggestions()).containsExactlyElementsIn(SUGGESTIONS);
+        assertThat(info.getExtras().getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
+    }
+
+    private static TextAttribute cloneViaParcel(TextAttribute src) {
+        final Parcel parcel = Parcel.obtain();
+        try {
+            src.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return TextAttribute.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java
new file mode 100644
index 0000000..afd5c73
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/TextSnapshotTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.view.inputmethod.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.text.InputType;
+import android.text.Selection;
+import android.view.inputmethod.SurroundingText;
+import android.view.inputmethod.TextSnapshot;
+import android.view.inputmethod.cts.util.InputConnectionTestUtils;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class TextSnapshotTest {
+
+    @Test
+    public void testConstruction() {
+        // Empty text
+        verify(surroundingText("[]", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+        // A single character
+        verify(surroundingText("0[]]", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+        verify(surroundingText("012[345]6789", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        verify(surroundingText("012[345]6789", 1), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        verify(surroundingText("012[345]6789", 2), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+
+        verify(surroundingText("012[345]6789", 0), -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        verify(surroundingText("012[345]6789", 0), 3, 6, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+        verify(surroundingText("012[345]6789", 2), 3, 6, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void testNullSurroundingText1() {
+        new TextSnapshot(null, -1, -1, InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidComposeRegion() {
+        new TextSnapshot(surroundingText("012[345]6789", 0), -2, -2,
+                InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidComposeRegion2() {
+        new TextSnapshot(surroundingText("012[345]6789", 0), 0, -2,
+                InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidComposeRegion3() {
+        new TextSnapshot(surroundingText("012[345]6789", 0), -2, 0,
+                InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testInvalidComposeRegion4() {
+        new TextSnapshot(surroundingText("012[345]6789", 0), 2, 1,
+                InputType.TYPE_TEXT_FLAG_CAP_WORDS);
+    }
+
+    private static SurroundingText surroundingText(@NonNull String formatString, int offset) {
+        final CharSequence text = InputConnectionTestUtils.formatString(formatString);
+        final int selectionStart = Selection.getSelectionStart(text);
+        assertThat(selectionStart).isGreaterThan(-1);
+        final int selectionEnd = Selection.getSelectionEnd(text);
+        assertThat(selectionEnd).isGreaterThan(-1);
+        return new SurroundingText(text.subSequence(offset, text.length() - offset),
+                selectionStart - offset, selectionEnd - offset, offset);
+    }
+
+    private static void verify(@NonNull SurroundingText surroundingText, int compositionStart,
+            int compositionEnd, int cursorCapsMode) {
+        final TextSnapshot snapshot =
+                new TextSnapshot(surroundingText, compositionStart, compositionEnd, cursorCapsMode);
+
+        assertThat(snapshot.getSurroundingText().getText()).isEqualTo(surroundingText.getText());
+        assertThat(snapshot.getSurroundingText().getOffset())
+                .isEqualTo(surroundingText.getOffset());
+        assertThat(snapshot.getSurroundingText().getSelectionStart())
+                .isEqualTo(surroundingText.getSelectionStart());
+        assertThat(snapshot.getSurroundingText().getSelectionEnd())
+                .isEqualTo(surroundingText.getSelectionEnd());
+
+        assertThat(snapshot.getSelectionStart())
+                .isEqualTo(surroundingText.getSelectionStart() + surroundingText.getOffset());
+        assertThat(snapshot.getSelectionEnd())
+                .isEqualTo(surroundingText.getSelectionEnd() + surroundingText.getOffset());
+
+        assertThat(snapshot.getCompositionStart()).isEqualTo(compositionStart);
+        assertThat(snapshot.getCompositionEnd()).isEqualTo(compositionEnd);
+
+        assertThat(snapshot.getCursorCapsMode()).isEqualTo(cursorCapsMode);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
index f2a64c4..52a3de5 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/EndToEndImeTestBase.java
@@ -20,8 +20,10 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
 
@@ -97,4 +99,15 @@
                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
         InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
     }
+
+    protected static boolean isPreventImeStartup() {
+        final Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try {
+            return context.getResources().getBoolean(
+                    android.R.bool.config_preventImeStartupUnlessTextEditor);
+        } catch (Resources.NotFoundException e) {
+            // Assume this is not enabled.
+            return false;
+        }
+    }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java
new file mode 100644
index 0000000..6992015
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/HandlerInputConnection.java
@@ -0,0 +1,190 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.KeyEvent;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
+import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.SurroundingText;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A null implementation of {@link InputConnection} except for {@link InputConnection#getHandler()}.
+ *
+ * <p>This is useful when testing callbacks on {@link InputConnection} are happening on the thread
+ * specified by {@link InputConnection#getHandler()}.</p>
+ */
+public class HandlerInputConnection implements InputConnection {
+    @Nullable
+    private final Handler mHandler;
+
+    protected HandlerInputConnection(@Nullable Handler handler) {
+        mHandler = handler;
+    }
+
+    @Override
+    public CharSequence getTextBeforeCursor(int n, int flags) {
+        return null;
+    }
+
+    @Override
+    public CharSequence getTextAfterCursor(int n, int flags) {
+        return null;
+    }
+
+    @Override
+    public CharSequence getSelectedText(int flags) {
+        return null;
+    }
+
+    @Override
+    public SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) {
+        return null;
+    }
+
+    @Override
+    public int getCursorCapsMode(int reqModes) {
+        return 0;
+    }
+
+    @Override
+    public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
+        return null;
+    }
+
+    @Override
+    public boolean deleteSurroundingText(int beforeLength, int afterLength) {
+        return false;
+    }
+
+    @Override
+    public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
+        return false;
+    }
+
+    @Override
+    public boolean setComposingText(CharSequence text, int newCursorPosition) {
+        return false;
+    }
+
+    @Override
+    public boolean setComposingRegion(int start, int end) {
+        return false;
+    }
+
+    @Override
+    public boolean finishComposingText() {
+        return false;
+    }
+
+    @Override
+    public boolean commitText(CharSequence text, int newCursorPosition) {
+        return false;
+    }
+
+    @Override
+    public boolean commitCompletion(CompletionInfo text) {
+        return false;
+    }
+
+    @Override
+    public boolean commitCorrection(CorrectionInfo correctionInfo) {
+        return false;
+    }
+
+    @Override
+    public boolean setSelection(int start, int end) {
+        return false;
+    }
+
+    @Override
+    public boolean performEditorAction(int editorAction) {
+        return false;
+    }
+
+    @Override
+    public boolean performContextMenuAction(int id) {
+        return false;
+    }
+
+    @Override
+    public boolean beginBatchEdit() {
+        return false;
+    }
+
+    @Override
+    public boolean endBatchEdit() {
+        return false;
+    }
+
+    @Override
+    public boolean sendKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    @Override
+    public boolean clearMetaKeyStates(int states) {
+        return false;
+    }
+
+    @Override
+    public boolean reportFullscreenMode(boolean enabled) {
+        return false;
+    }
+
+    @Override
+    public boolean performSpellCheck() {
+        return false;
+    }
+
+    @Override
+    public boolean performPrivateCommand(String action, Bundle data) {
+        return false;
+    }
+
+    @Override
+    public boolean requestCursorUpdates(int cursorUpdateMode) {
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @Override
+    public void closeConnection() {
+    }
+
+    @Override
+    public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
+        return false;
+    }
+
+    @Override
+    public boolean setImeConsumesInput(boolean imeConsumesInput) {
+        return false;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
index c245b9a1..c04035a 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
@@ -16,10 +16,12 @@
 
 package android.view.inputmethod.cts.util;
 
+import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Instrumentation;
 import android.app.UiAutomation;
+import android.graphics.Bitmap;
 import android.os.SystemClock;
 
 import androidx.annotation.NonNull;
@@ -47,12 +49,14 @@
 
     @NonNull
     private static boolean containsWatermark(@NonNull UiAutomation uiAutomation) {
-        return Watermark.detect(uiAutomation.takeScreenshot());
+        final Bitmap screenshot = uiAutomation.takeScreenshot();
+        assertNotNull(screenshot);
+        return Watermark.detect(screenshot);
     }
 
     @NonNull
     private static boolean notContainsWatermark(@NonNull UiAutomation uiAutomation) {
-        return !Watermark.detect(uiAutomation.takeScreenshot());
+        return !containsWatermark(uiAutomation);
     }
 
     private static boolean waitUntil(long timeout, @NonNull Predicate<UiAutomation> condition) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.java
new file mode 100644
index 0000000..84e9d18
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/SimulatedVirtualDisplaySession.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.view.inputmethod.cts.util;
+
+import static android.hardware.display.DisplayManager.DISPLAY_CATEGORY_PRESENTATION;
+import static android.provider.Settings.Global.OVERLAY_DISPLAY_DEVICES;
+import static android.view.Display.FLAG_TRUSTED;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+import android.content.Context;
+import android.hardware.display.DisplayManager;
+import android.util.Size;
+import android.view.Display;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.CommonTestUtils;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.StringJoiner;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * A session to create/close the simulated overlay display for testing multi-display behavior.
+ */
+public final class SimulatedVirtualDisplaySession implements AutoCloseable {
+
+    private static final String OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS =
+            "should_show_system_decorations";
+
+    private final Display mDisplay;
+    private SimulatedVirtualDisplaySession(@NonNull Display display) {
+        mDisplay = display;
+    }
+
+    public static SimulatedVirtualDisplaySession create(@NonNull Context context,
+            int width, int height, int density, int displayImePolicy) {
+        final String displaySettingsStr = new StringJoiner(",")
+                // Display size/density configuration
+                .add(new Size(width, height) + "/" + density)
+                // Support system decoration to show IME on the simulated display by default
+                .add(OVERLAY_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS)
+                .toString();
+        putGlobalSetting(OVERLAY_DISPLAY_DEVICES, displaySettingsStr);
+
+        final DisplayManager dm = context.getSystemService(DisplayManager.class);
+        final WindowManager wm = context.getSystemService(WindowManager.class);
+        AtomicReference<Display> simulatedDisplay = new AtomicReference();
+
+        try {
+            CommonTestUtils.waitUntil("No simulated display found", 5,
+                    () -> {
+                        final Display[] displays = dm.getDisplays(DISPLAY_CATEGORY_PRESENTATION);
+                        for (Display display : displays) {
+                            final boolean isTrusted =
+                                    (display.getFlags() & FLAG_TRUSTED) == FLAG_TRUSTED;
+                            if (isTrusted && display.getType() == Display.TYPE_OVERLAY) {
+                                SystemUtil.runWithShellPermissionIdentity(() -> {
+                                    wm.setDisplayImePolicy(display.getDisplayId(),
+                                            displayImePolicy);
+                                    simulatedDisplay.set(display);
+                                });
+                                return true;
+                            }
+                        }
+                        return false;
+                    });
+        } catch (AssertionError | Exception e) {
+            deleteGlobalSetting(OVERLAY_DISPLAY_DEVICES);
+            throw new RuntimeException("Exception!", e);
+        }
+        return new SimulatedVirtualDisplaySession(simulatedDisplay.get());
+    }
+
+    private static void putGlobalSetting(String key, String value) {
+        runShellCommand("settings put global " + key + " " + value);
+    }
+
+    private static void deleteGlobalSetting(String key) {
+        runShellCommand("settings delete global " + key);
+    }
+
+    public int getDisplayId() {
+        return mDisplay.getDisplayId();
+    }
+
+    @Override
+    public void close() throws Exception {
+        deleteGlobalSetting(OVERLAY_DISPLAY_DEVICES);
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
index 398e7b6..66ef1e2 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -16,26 +16,36 @@
 
 package android.view.inputmethod.cts.util;
 
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
+import android.content.Context;
 import android.content.Intent;
+import android.graphics.PixelFormat;
 import android.os.Bundle;
 import android.view.View;
 import android.view.Window;
 import android.view.WindowManager;
+import android.widget.TextView;
 
 import androidx.annotation.AnyThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.UiThread;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Function;
 
 public class TestActivity extends Activity {
 
+    public static final String OVERLAY_WINDOW_NAME = "TestActivity.APP_OVERLAY_WINDOW";
     private static final AtomicReference<Function<TestActivity, View>> sInitializer =
             new AtomicReference<>();
 
@@ -45,6 +55,8 @@
 
     private long mOnBackPressedCallCount;
 
+    private TextView mOverlayView;
+
     /**
      * Controls how {@link #onBackPressed()} behaves.
      *
@@ -82,6 +94,16 @@
         setContentView(mInitializer.apply(this));
     }
 
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mOverlayView != null) {
+            mOverlayView.getContext()
+                    .getSystemService(WindowManager.class).removeView(mOverlayView);
+            mOverlayView = null;
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
@@ -94,6 +116,23 @@
         super.onBackPressed();
     }
 
+    public void showOverlayWindow() {
+        if (mOverlayView != null) {
+            throw new IllegalStateException("can only show one overlay at a time.");
+        }
+        Context overlayContext = getApplicationContext().createWindowContext(getDisplay(),
+                TYPE_APPLICATION_OVERLAY, null);
+        mOverlayView = new TextView(overlayContext);
+        WindowManager.LayoutParams params =
+                new WindowManager.LayoutParams(MATCH_PARENT, MATCH_PARENT,
+                        TYPE_APPLICATION_OVERLAY, FLAG_NOT_FOCUSABLE, PixelFormat.TRANSLUCENT);
+        params.setTitle(OVERLAY_WINDOW_NAME);
+        mOverlayView.setLayoutParams(params);
+        mOverlayView.setText("IME CTS TestActivity OverlayView");
+        mOverlayView.setBackgroundColor(0x77FFFF00);
+        overlayContext.getSystemService(WindowManager.class).addView(mOverlayView, params);
+    }
+
     /**
      * Launches {@link TestActivity} with the given initialization logic for content view.
      *
@@ -111,6 +150,30 @@
     }
 
     /**
+     * Similar to {@link TestActivity#startSync(Function)}, but with the given display ID to
+     * specify the launching target display.
+     * @param displayId The ID of the display
+     * @param activityInitializer initializer to supply {@link View} to be passed to
+     *                            {@link Activity#setContentView(View)}
+     * @return {@link TestActivity} launched
+     */
+    public static TestActivity startSync(int displayId,
+            @NonNull Function<TestActivity, View> activityInitializer) throws Exception {
+        sInitializer.set(activityInitializer);
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(displayId);
+        final Intent intent = new Intent()
+                .setAction(Intent.ACTION_MAIN)
+                .setClass(InstrumentationRegistry.getInstrumentation().getContext(),
+                        TestActivity.class)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        return SystemUtil.callWithShellPermissionIdentity(
+                () -> (TestActivity) InstrumentationRegistry.getInstrumentation().startActivitySync(
+                        intent, options.toBundle()));
+    }
+
+    /**
      * Launches {@link TestActivity} with the given initialization logic for content view.
      *
      * <p>As long as you are using {@link androidx.test.runner.AndroidJUnitRunner}, the test
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 207c440..3963132 100644
--- a/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
+++ b/tests/inputmethod/testapp/src/android/view/inputmethod/ctstestapp/MainActivity.java
@@ -77,7 +77,8 @@
             }
             return uri.getBooleanQueryParameter(key, false);
         }
-        return getIntent().getBooleanExtra(key, false);
+        return getIntent().getBooleanExtra(key, false)
+                || "true".equals(getIntent().getStringExtra(key));
     }
 
     @Override
@@ -141,7 +142,8 @@
                 }
             }
         };
-        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER));
+        registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION_TRIGGER),
+                Context.RECEIVER_EXPORTED);
     }
 
     @Override
diff --git a/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java b/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java
deleted file mode 100644
index 1926c0d..0000000
--- a/tests/leanbackjank/app/src/android/leanbackjank/app/Utils.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.leanbackjank.app;
-
-import com.android.compatibility.common.util.ApiLevelUtil;
-
-import android.app.Activity;
-import android.content.Context;
-import android.graphics.Point;
-import android.media.MediaMetadataRetriever;
-import android.os.Build;
-import android.util.DisplayMetrics;
-import android.view.Display;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-import android.widget.Toast;
-import android.widget.VideoView;
-
-import java.util.HashMap;
-
-/**
- * A collection of utility methods, all static.
- */
-public class Utils {
-
-    public interface MediaDimensions {
-        double MEDIA_HEIGHT = 0.95;
-        double MEDIA_WIDTH = 0.95;
-        double MEDIA_TOP_MARGIN = 0.025;
-        double MEDIA_RIGHT_MARGIN = 0.025;
-        double MEDIA_BOTTOM_MARGIN = 0.025;
-        double MEDIA_LEFT_MARGIN = 0.025;
-    }
-
-    /*
-     * Making sure public utility methods remain static
-     */
-    private Utils() {
-    }
-
-    /**
-     * Returns the screen/display size
-     */
-    public static Point getDisplaySize(Context context) {
-        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        Display display = wm.getDefaultDisplay();
-        Point size = new Point();
-        display.getSize(size);
-        return size;
-    }
-
-    /**
-     * Shows a (long) toast
-     */
-    public static void showToast(Context context, String msg) {
-        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
-    }
-
-    /**
-     * Shows a (long) toast.
-     */
-    public static void showToast(Context context, int resourceId) {
-        Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
-    }
-
-    public static int convertDpToPixel(Context ctx, int dp) {
-        float density = ctx.getResources().getDisplayMetrics().density;
-        return Math.round((float) dp * density);
-    }
-
-
-    /**
-     * Example for handling resizing content for overscan.  Typically you won't need to resize
-     * when using the Leanback support library.
-     */
-    public void overScan(Activity activity, VideoView videoView) {
-        DisplayMetrics metrics = new DisplayMetrics();
-        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
-        int w = (int) (metrics.widthPixels * MediaDimensions.MEDIA_WIDTH);
-        int h = (int) (metrics.heightPixels * MediaDimensions.MEDIA_HEIGHT);
-        int marginLeft = (int) (metrics.widthPixels * MediaDimensions.MEDIA_LEFT_MARGIN);
-        int marginTop = (int) (metrics.heightPixels * MediaDimensions.MEDIA_TOP_MARGIN);
-        int marginRight = (int) (metrics.widthPixels * MediaDimensions.MEDIA_RIGHT_MARGIN);
-        int marginBottom = (int) (metrics.heightPixels * MediaDimensions.MEDIA_BOTTOM_MARGIN);
-        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
-        lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
-        videoView.setLayoutParams(lp);
-    }
-
-
-    public static long getDuration(String videoUrl) {
-        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
-        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.ICE_CREAM_SANDWICH)) {
-            mmr.setDataSource(videoUrl, new HashMap<String, String>());
-        } else {
-            mmr.setDataSource(videoUrl);
-        }
-        return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
-    }
-}
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 1addbfd..5a84d94 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -33,6 +33,8 @@
     optimize: {
         enabled: false,
     },
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/libcore/luni/Android.bp b/tests/libcore/luni/Android.bp
index 2fc1f56..f7ef2e7 100644
--- a/tests/libcore/luni/Android.bp
+++ b/tests/libcore/luni/Android.bp
@@ -58,6 +58,8 @@
     compile_multilib: "both",
     // This test requires cts-dalvik-host-test-runner to be built to run via Atest.
     host_required: ["cts-dalvik-host-test-runner"],
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index 1276621..5a9691b 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -37,6 +37,8 @@
     // Include both the 32 and 64 bit versions of libjavacoretests,
     // where applicable.
     compile_multilib: "both",
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index 4e0742f..12d3496 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -27,6 +27,8 @@
     optimize: {
         enabled: false,
     },
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/libcore/wycheproof-bc/Android.bp b/tests/libcore/wycheproof-bc/Android.bp
index 9f31747..c2c58dd 100644
--- a/tests/libcore/wycheproof-bc/Android.bp
+++ b/tests/libcore/wycheproof-bc/Android.bp
@@ -39,6 +39,8 @@
     // Include both the 32 and 64 bit versions of libjavacoretests,
     // where applicable.
     compile_multilib: "both",
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/location/common/src/android/location/cts/common/BroadcastCapture.java b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
index f2ea3ae..6ea51f7 100644
--- a/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
+++ b/tests/location/common/src/android/location/cts/common/BroadcastCapture.java
@@ -41,7 +41,8 @@
     }
 
     protected void register(String action) {
-        mContext.registerReceiver(this, new IntentFilter(action));
+        mContext.registerReceiver(this, new IntentFilter(action),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
     }
 
     public Intent getNextIntent(long timeoutMs) throws InterruptedException {
diff --git a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
index 69b1f0c..08ddca1 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/GeocoderTest.java
@@ -16,56 +16,57 @@
 
 package android.location.cts.fine;
 
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
+import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
 import android.location.Geocoder;
+import android.location.Geocoder.GeocodeListener;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.android.compatibility.common.util.RetryRule;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.IOException;
 import java.util.Locale;
 
 @RunWith(AndroidJUnit4.class)
 public class GeocoderTest {
 
-    private static final int MAX_NUM_RETRIES = 2;
-    private static final int TIME_BETWEEN_RETRIES_MS = 2000;
+    // retry just in case of network failure
+    @Rule
+    public final RetryRule mRetryRule = new RetryRule(2);
 
     private Context mContext;
+    private Geocoder mGeocoder;
 
     @Before
     public void setUp() {
         mContext = ApplicationProvider.getApplicationContext();
-    }
-
-    @Test
-    public void testConstructor() {
-        new Geocoder(mContext);
-
-        new Geocoder(mContext, Locale.ENGLISH);
-
-        try {
-            new Geocoder(mContext, null);
-            fail("should throw NullPointerException.");
-        } catch (NullPointerException e) {
-            // expected.
-        }
+        mGeocoder = new Geocoder(mContext, Locale.US);
     }
 
     @Test
     public void testIsPresent() {
-        if (isServiceMissing()) {
+        if (mContext.getPackageManager().queryIntentServices(
+                new Intent("com.android.location.service.GeocodeProvider"), ResolveInfoFlags.of(
+                        MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE)).isEmpty()) {
             assertFalse(Geocoder.isPresent());
         } else {
             assertTrue(Geocoder.isPresent());
@@ -73,129 +74,62 @@
     }
 
     @Test
-    public void testGetFromLocation() throws IOException, InterruptedException {
-        Geocoder geocoder = new Geocoder(mContext);
+    public void testGetFromLocation() {
+        assumeTrue(Geocoder.isPresent());
 
-        // There is no guarantee that geocoder.getFromLocation returns accurate results
-        // Thus only test that calling the method with valid arguments doesn't produce
-        // an unexpected exception
-        // Note: there is a risk this test will fail if device under test does not have
-        // a network connection. This is why we try the geocode 5 times if it fails due
-        // to a network error.
-        int numRetries = 0;
-        while (numRetries < MAX_NUM_RETRIES) {
-            try {
-                geocoder.getFromLocation(60, 30, 5);
-                break;
-            } catch (IOException e) {
-                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
-                numRetries++;
-            }
-        }
-        if (numRetries >= MAX_NUM_RETRIES) {
-            fail("Failed to geocode location " + MAX_NUM_RETRIES + " times.");
-        }
-
-
-        try {
-            // latitude is less than -90
-            geocoder.getFromLocation(-91, 30, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            // latitude is greater than 90
-            geocoder.getFromLocation(91, 30, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            // longitude is less than -180
-            geocoder.getFromLocation(10, -181, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            // longitude is greater than 180
-            geocoder.getFromLocation(10, 181, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
+        GeocodeListener listener = mock(GeocodeListener.class);
+        mGeocoder.getFromLocation(60, 30, 5, listener);
+        verify(listener, timeout(10000)).onGeocode(anyList());
     }
 
     @Test
-    public void testGetFromLocationName() throws IOException, InterruptedException {
-        Geocoder geocoder = new Geocoder(mContext, Locale.US);
+    public void testGetFromLocation_sync() throws Exception {
+        assumeTrue(Geocoder.isPresent());
 
-        // There is no guarantee that geocoder.getFromLocationName returns accurate results.
-        // Thus only test that calling the method with valid arguments doesn't produce
-        // an unexpected exception
-        // Note: there is a risk this test will fail if device under test does not have
-        // a network connection. This is why we try the geocode 5 times if it fails due
-        // to a network error.
-        int numRetries = 0;
-        while (numRetries < MAX_NUM_RETRIES) {
-            try {
-                geocoder.getFromLocationName("Dalvik,Iceland", 5);
-                break;
-            } catch (IOException e) {
-                Thread.sleep(TIME_BETWEEN_RETRIES_MS);
-                numRetries++;
-            }
-        }
-        if (numRetries >= MAX_NUM_RETRIES) {
-            fail("Failed to geocode location name " + MAX_NUM_RETRIES + " times.");
-        }
-
-        try {
-            geocoder.getFromLocationName(null, 5);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
-
-        try {
-            geocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181);
-            fail("should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-            // pass
-        }
+        mGeocoder.getFromLocation(60, 30, 5);
     }
 
-    private boolean isServiceMissing() {
-        PackageManager pm = mContext.getPackageManager();
+    @Test
+    public void testGetFromLocation_badInput() {
+        GeocodeListener listener = mock(GeocodeListener.class);
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocation(-91, 30, 5, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocation(91, 30, 5, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocation(10, -181, 5, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocation(10, 181, 5, listener));
+    }
 
-        final Intent intent = new Intent("com.android.location.service.GeocodeProvider");
-        final int flags = PackageManager.MATCH_DIRECT_BOOT_AWARE
-                | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
-        return pm.queryIntentServices(intent, flags).isEmpty();
+    @Test
+    public void testGetFromLocationName() {
+        assumeTrue(Geocoder.isPresent());
+
+        GeocodeListener listener = mock(GeocodeListener.class);
+        mGeocoder.getFromLocationName("Dalvik,Iceland", 5, listener);
+        verify(listener, timeout(10000)).onGeocode(anyList());
+    }
+
+    @Test
+    public void testGetFromLocationName_sync() throws Exception {
+        assumeTrue(Geocoder.isPresent());
+
+        mGeocoder.getFromLocationName("Dalvik,Iceland", 5);
+    }
+
+    @Test
+    public void testGetFromLocationName_badInput() {
+        GeocodeListener listener = mock(GeocodeListener.class);
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocationName(null, 5, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocationName("Beijing", 5, -91, 100, 45, 130, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 190, 45, 130, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 100, 91, 130, listener));
+        assertThrows(IllegalArgumentException.class,
+                () -> mGeocoder.getFromLocationName("Beijing", 5, 25, 100, 45, -181, listener));
     }
 }
diff --git a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
index 6d0ac08..af9ecff 100644
--- a/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
+++ b/tests/location/location_fine/src/android/location/cts/fine/ScanningSettingsTest.java
@@ -155,6 +155,9 @@
             // Scrolling can fail if the UI is not scrollable
         }
 
+        // Wait for the preference to appear
+        mDevice.wait(Until.hasObject(By.text(res.getString(resId))), TIMEOUT);
+
         UiObject2 pref = mDevice.findObject(By.text(res.getString(resId)));
         // Click the preference to show the Scanning fragment
         pref.click();
diff --git a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
index f615d51..320cf62 100644
--- a/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
+++ b/tests/location/location_gnss/src/android/location/cts/gnss/GnssMeasurementRegistrationTest.java
@@ -16,11 +16,14 @@
 
 package android.location.cts.gnss;
 
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
 import android.location.GnssMeasurement;
 import android.location.GnssMeasurementRequest;
 import android.location.GnssMeasurementsEvent;
 import android.location.GnssStatus;
-import android.location.cts.common.GnssTestCase;
 import android.location.cts.common.SoftAssert;
 import android.location.cts.common.TestGnssMeasurementListener;
 import android.location.cts.common.TestLocationListener;
@@ -28,6 +31,14 @@
 import android.location.cts.common.TestMeasurementUtil;
 import android.util.Log;
 
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
 import java.util.List;
 
 /**
@@ -50,23 +61,25 @@
  *              Android Q, it is mandatory to report GnssMeasurement even if a location has not
  *              yet been reported. Therefore, the test fails.
  */
-public class GnssMeasurementRegistrationTest extends GnssTestCase {
+@RunWith(JUnit4.class)
+public class GnssMeasurementRegistrationTest {
 
     private static final String TAG = "GnssMeasRegTest";
     private static final int EVENTS_COUNT = 5;
     private static final int GPS_EVENTS_COUNT = 1;
     private TestLocationListener mLocationListener;
     private TestGnssMeasurementListener mMeasurementListener;
+    private TestLocationManager mTestLocationManager;
+    private Context mContext;
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
-        mTestLocationManager = new TestLocationManager(getContext());
+    @Before
+    public void setUp() throws Exception {
+        mContext = ApplicationProvider.getApplicationContext();
+        mTestLocationManager = new TestLocationManager(mContext);
     }
 
-    @Override
-    protected void tearDown() throws Exception {
+    @After
+    public void tearDown() throws Exception {
         // Unregister listeners
         if (mLocationListener != null) {
             mTestLocationManager.removeLocationUpdates(mLocationListener);
@@ -74,22 +87,16 @@
         if (mMeasurementListener != null) {
             mTestLocationManager.unregisterGnssMeasurementCallback(mMeasurementListener);
         }
-        super.tearDown();
     }
 
     /**
      * Test GPS measurements registration.
      */
+    @Test
     public void testGnssMeasurementRegistration() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
-            return;
-        }
-
-        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
-            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
-            return;
-        }
+        assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+        assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+                TestMeasurementUtil.isAutomotiveDevice(mContext));
 
         // Register for GPS measurements.
         mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
@@ -101,16 +108,11 @@
     /**
      * Test GPS measurements registration with full tracking enabled.
      */
+    @Test
     public void testGnssMeasurementRegistration_enableFullTracking() throws Exception {
-        // Checks if GPS hardware feature is present, skips test (pass) if not,
-        if (!TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG)) {
-            return;
-        }
-
-        if (TestMeasurementUtil.isAutomotiveDevice(getContext())) {
-            Log.i(TAG, "Test is being skipped because the system has the AUTOMOTIVE feature.");
-            return;
-        }
+        assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+        assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+                TestMeasurementUtil.isAutomotiveDevice(mContext));
 
         // Register for GPS measurements.
         mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
@@ -120,6 +122,23 @@
         verifyGnssMeasurementsReceived();
     }
 
+    /**
+     * Test GPS measurements registration with 2s interval.
+     */
+    @Test
+    public void testGnssMeasurementRegistration_2secInterval() throws Exception {
+        assumeTrue(TestMeasurementUtil.canTestRunOnCurrentDevice(mTestLocationManager, TAG));
+        assumeFalse("Test is being skipped because the system has the AUTOMOTIVE feature.",
+                TestMeasurementUtil.isAutomotiveDevice(mContext));
+
+        // Register for GPS measurements.
+        mMeasurementListener = new TestGnssMeasurementListener(TAG, GPS_EVENTS_COUNT);
+        mTestLocationManager.registerGnssMeasurementCallback(mMeasurementListener,
+                new GnssMeasurementRequest.Builder().setIntervalMillis(2000).build());
+
+        verifyGnssMeasurementsReceived();
+    }
+
     private void verifyGnssMeasurementsReceived() throws InterruptedException {
         mMeasurementListener.await();
 
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
index 584a26e..640ecf6 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAntennaInfoTest.java
@@ -118,6 +118,24 @@
         verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfo);
     }
 
+    @Test
+    public void testGnssAntennaInfoCopyBuilder() {
+        GnssAntennaInfo gnssAntennaInfo = createFullTestGnssAntennaInfo();
+        GnssAntennaInfo gnssAntennaInfoCopy = new GnssAntennaInfo.Builder(gnssAntennaInfo).build();
+
+        verifyFullGnssAntennaInfoValuesAndGetters(gnssAntennaInfoCopy);
+    }
+
+    @Test
+    public void testGnssAntennaInfoBuilderWithArguments() {
+        GnssAntennaInfo gnssAntennaInfo = createPartialTestGnssAntennaInfo();
+        GnssAntennaInfo gnssAntennaInfoCopy = new GnssAntennaInfo.Builder(
+                gnssAntennaInfo.getCarrierFrequencyMHz(),
+                gnssAntennaInfo.getPhaseCenterOffset()).build();
+
+        verifyPartialGnssAntennaInfoValuesAndGetters(gnssAntennaInfoCopy);
+    }
+
     private static GnssAntennaInfo createFullTestGnssAntennaInfo() {
         double carrierFrequencyMHz = 13758.0;
 
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java b/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java
new file mode 100644
index 0000000..8e8f112
--- /dev/null
+++ b/tests/location/location_none/src/android/location/cts/none/GnssAutomaticGainControlTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location.cts.none;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.location.GnssAutomaticGainControl;
+import android.location.GnssStatus;
+import android.os.Parcel;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests fundamental functionality of {@link GnssAutomaticGainControl} class. This includes writing
+ * and reading from parcel, and verifying computed values and getters.
+ */
+@RunWith(AndroidJUnit4.class)
+public class GnssAutomaticGainControlTest {
+    private static final float DELTA = 1e-3f;
+
+    @Test
+    public void testGetValues() {
+        GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+                3.5).build();
+        assertEquals(GnssStatus.CONSTELLATION_GPS, agc.getConstellationType());
+        assertEquals(1575420000, agc.getCarrierFrequencyHz());
+        assertEquals(3.5, agc.getLevelDb(), DELTA);
+    }
+
+    @Test
+    public void testDescribeContents() {
+        GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().build();
+        assertEquals(agc.describeContents(), 0);
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+                3.5).build();
+
+        Parcel parcel = Parcel.obtain();
+        agc.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        GnssAutomaticGainControl fromParcel = GnssAutomaticGainControl.CREATOR.createFromParcel(
+                parcel);
+
+        assertEquals(agc, fromParcel);
+    }
+
+    @Test
+    public void testEquals() {
+        GnssAutomaticGainControl agc1 =
+                new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                        1575420000).setConstellationType(GnssStatus.CONSTELLATION_GPS).setLevelDb(
+                        3.5).build();
+        GnssAutomaticGainControl agc2 = new GnssAutomaticGainControl.Builder(agc1).build();
+        GnssAutomaticGainControl agc3 =
+                new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                        1602000000).setConstellationType(
+                        GnssStatus.CONSTELLATION_GLONASS).setLevelDb(-2.8).build();
+
+        assertEquals(agc1, agc2);
+        assertNotEquals(agc1, agc3);
+    }
+}
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
index feb44d8..2971052 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementRequestTest.java
@@ -1,5 +1,7 @@
 package android.location.cts.none;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -19,30 +21,33 @@
  */
 @RunWith(AndroidJUnit4.class)
 public class GnssMeasurementRequestTest {
-
-    private GnssMeasurementRequest getTestGnssMeasurementRequest(boolean fullTracking) {
-        GnssMeasurementRequest.Builder builder = new GnssMeasurementRequest.Builder();
-        builder.setFullTracking(fullTracking);
-        return builder.build();
-    }
+    private static final int TEST_INTERVAL_MS = 2000;
 
     @Test
     public void testGetValues() {
-        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request1 =
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build();
         assertTrue(request1.isFullTracking());
-        GnssMeasurementRequest request2 = getTestGnssMeasurementRequest(false);
+        GnssMeasurementRequest request2 =
+                new GnssMeasurementRequest.Builder().setFullTracking(false).build();
         assertFalse(request2.isFullTracking());
+
+        GnssMeasurementRequest request3 =
+                new GnssMeasurementRequest.Builder().setIntervalMillis(TEST_INTERVAL_MS).build();
+        assertThat(request3.getIntervalMillis()).isEqualTo(TEST_INTERVAL_MS);
     }
 
     @Test
     public void testDescribeContents() {
-        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request =
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build();
         assertEquals(request.describeContents(), 0);
     }
 
     @Test
     public void testWriteToParcel() {
-        GnssMeasurementRequest request = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request =
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build();
 
         Parcel parcel = Parcel.obtain();
         request.writeToParcel(parcel, 0);
@@ -54,10 +59,15 @@
 
     @Test
     public void testEquals() {
-        GnssMeasurementRequest request1 = getTestGnssMeasurementRequest(true);
+        GnssMeasurementRequest request1 =
+                new GnssMeasurementRequest.Builder().setFullTracking(true).build();
         GnssMeasurementRequest request2 = new GnssMeasurementRequest.Builder(request1).build();
-        GnssMeasurementRequest request3 = getTestGnssMeasurementRequest(false);
+        GnssMeasurementRequest request3 =
+                new GnssMeasurementRequest.Builder().setFullTracking(false).build();
+        GnssMeasurementRequest request4 =
+                new GnssMeasurementRequest.Builder().setIntervalMillis(TEST_INTERVAL_MS).build();
         assertEquals(request1, request2);
         assertNotEquals(request3, request2);
+        assertNotEquals(request4, request3);
     }
 }
diff --git a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
index dc1c56f..870bb5d 100644
--- a/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/GnssMeasurementsEventTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.location.GnssAutomaticGainControl;
 import android.location.GnssClock;
 import android.location.GnssMeasurement;
 import android.location.GnssMeasurementsEvent;
@@ -31,6 +32,7 @@
 
 import java.util.Collection;
 import java.util.Iterator;
+import java.util.List;
 
 @RunWith(AndroidJUnit4.class)
 public class GnssMeasurementsEventTest {
@@ -40,8 +42,10 @@
         GnssClock clock = new GnssClock();
         GnssMeasurement m1 = new GnssMeasurement();
         GnssMeasurement m2 = new GnssMeasurement();
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
+        GnssAutomaticGainControl agc = new GnssAutomaticGainControl.Builder().build();
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent.Builder().setClock(clock)
+                .setMeasurements(List.of(m1, m2))
+                .setGnssAutomaticGainControls(List.of(agc)).build();
         assertEquals(0, event.describeContents());
     }
 
@@ -53,12 +57,22 @@
         m1.setConstellationType(GnssStatus.CONSTELLATION_GLONASS);
         GnssMeasurement m2 = new GnssMeasurement();
         m2.setReceivedSvTimeNanos(43999);
-        GnssMeasurementsEvent event = new GnssMeasurementsEvent(
-                clock, new GnssMeasurement[] {m1, m2});
+        GnssAutomaticGainControl agc1 =
+                new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                        1575420000).setConstellationType(
+                        GnssStatus.CONSTELLATION_GPS).setLevelDb(3.5).build();
+        GnssAutomaticGainControl agc2 =
+                new GnssAutomaticGainControl.Builder().setCarrierFrequencyHz(
+                        1602000000).setConstellationType(
+                        GnssStatus.CONSTELLATION_GLONASS).setLevelDb(-2.8).build();
+        GnssMeasurementsEvent event = new GnssMeasurementsEvent.Builder().setClock(clock)
+                .setMeasurements(List.of(m1, m2))
+                .setGnssAutomaticGainControls(List.of(agc1, agc2)).build();
         Parcel parcel = Parcel.obtain();
         event.writeToParcel(parcel, 0);
         parcel.setDataPosition(0);
         GnssMeasurementsEvent newEvent = GnssMeasurementsEvent.CREATOR.createFromParcel(parcel);
+
         assertEquals(100, newEvent.getClock().getLeapSecond());
         Collection<GnssMeasurement> measurements = newEvent.getMeasurements();
         assertEquals(2, measurements.size());
@@ -67,5 +81,13 @@
         assertEquals(GnssStatus.CONSTELLATION_GLONASS, newM1.getConstellationType());
         GnssMeasurement newM2 = iterator.next();
         assertEquals(43999, newM2.getReceivedSvTimeNanos());
+
+        Collection<GnssAutomaticGainControl> agcs = newEvent.getGnssAutomaticGainControls();
+        assertEquals(2, agcs.size());
+        Iterator<GnssAutomaticGainControl> gnssAgcIterator = agcs.iterator();
+        GnssAutomaticGainControl newAgc1 = gnssAgcIterator.next();
+        assertEquals(newAgc1, agc1);
+        GnssAutomaticGainControl newAgc2 = gnssAgcIterator.next();
+        assertEquals(newAgc2, agc2);
     }
 }
diff --git a/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
index 670b536..ed2c11c 100644
--- a/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LastLocationRequestTest.java
@@ -33,6 +33,7 @@
     public void testBuild_Defaults() {
         LastLocationRequest request = new LastLocationRequest.Builder().build();
         assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+        assertThat(request.isAdasGnssBypass()).isEqualTo(false);
         assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
     }
 
@@ -40,9 +41,11 @@
     public void testBuild_Explicit() {
         LastLocationRequest request = new LastLocationRequest.Builder()
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .build();
         assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(request.isAdasGnssBypass()).isEqualTo(true);
         assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
     }
 
@@ -50,10 +53,12 @@
     public void testBuild_Copy() {
         LastLocationRequest original = new LastLocationRequest.Builder()
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .build();
         LastLocationRequest copy = new LastLocationRequest.Builder(original).build();
         assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(copy.isAdasGnssBypass()).isEqualTo(true);
         assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
         assertThat(copy).isEqualTo(original);
     }
@@ -68,6 +73,7 @@
     public void testParcelRoundtrip() {
         LastLocationRequest request = new LastLocationRequest.Builder()
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .build();
 
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
index 97b57af..8f6dd3f 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationRequestTest.java
@@ -43,6 +43,7 @@
         assertThat(request.getMaxUpdates()).isEqualTo(Integer.MAX_VALUE);
         assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(0f);
         assertThat(request.isHiddenFromAppOps()).isEqualTo(false);
+        assertThat(request.isAdasGnssBypass()).isEqualTo(false);
         assertThat(request.isLocationSettingsIgnored()).isEqualTo(false);
         assertThat(request.isLowPower()).isEqualTo(false);
     }
@@ -56,6 +57,7 @@
                 .setMaxUpdates(7000)
                 .setMinUpdateDistanceMeters(8000f)
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .setLowPower(true)
                 .build();
@@ -66,6 +68,7 @@
         assertThat(request.getMaxUpdates()).isEqualTo(7000);
         assertThat(request.getMinUpdateDistanceMeters()).isEqualTo(8000f);
         assertThat(request.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(request.isAdasGnssBypass()).isEqualTo(true);
         assertThat(request.isLocationSettingsIgnored()).isEqualTo(true);
         assertThat(request.isLowPower()).isEqualTo(true);
     }
@@ -79,6 +82,7 @@
                 .setMaxUpdates(7000)
                 .setMinUpdateDistanceMeters(8000f)
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .setLowPower(true)
                 .build();
@@ -90,6 +94,7 @@
         assertThat(copy.getMaxUpdates()).isEqualTo(7000);
         assertThat(copy.getMinUpdateDistanceMeters()).isEqualTo(8000f);
         assertThat(copy.isHiddenFromAppOps()).isEqualTo(true);
+        assertThat(copy.isAdasGnssBypass()).isEqualTo(true);
         assertThat(copy.isLocationSettingsIgnored()).isEqualTo(true);
         assertThat(copy.isLowPower()).isEqualTo(true);
         assertThat(copy).isEqualTo(original);
@@ -178,6 +183,7 @@
                 .setMaxUpdates(7000)
                 .setMinUpdateDistanceMeters(8000f)
                 .setHiddenFromAppOps(true)
+                .setAdasGnssBypass(true)
                 .setLocationSettingsIgnored(true)
                 .setLowPower(true)
                 .build();
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationTest.java b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
index 12b93046..87a15b8 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationTest.java
@@ -16,11 +16,10 @@
 
 package android.location.cts.none;
 
+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;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.location.Location;
@@ -38,90 +37,143 @@
 @RunWith(AndroidJUnit4.class)
 public class LocationTest {
 
-    private static final float DELTA = 0.1f;
-    private final float TEST_ACCURACY = 1.0f;
-    private final float TEST_VERTICAL_ACCURACY = 2.0f;
-    private final float TEST_SPEED_ACCURACY = 3.0f;
-    private final float TEST_BEARING_ACCURACY = 4.0f;
-    private final double TEST_ALTITUDE = 1.0;
-    private final double TEST_LATITUDE = 50;
-    private final float TEST_BEARING = 1.0f;
-    private final double TEST_LONGITUDE = 20;
-    private final float TEST_SPEED = 5.0f;
-    private final long TEST_TIME = 100;
-    private final String TEST_PROVIDER = "LocationProvider";
-    private final String TEST_KEY1NAME = "key1";
-    private final String TEST_KEY2NAME = "key2";
-    private final boolean TEST_KEY1VALUE = false;
-    private final byte TEST_KEY2VALUE = 10;
+    private static final float DELTA = 0.01f;
 
     @Test
-    public void testConstructor() {
-        new Location("LocationProvider");
+    public void testConstructor_Defaults() {
+        Location l = new Location("provider");
 
-        Location l = createTestLocation();
-        Location location = new Location(l);
-        assertTestLocation(location);
+        assertThat(l.getProvider()).isEqualTo("provider");
+        assertThat(l.getTime()).isEqualTo(0);
+        assertThat(l.getElapsedRealtimeNanos()).isEqualTo(0);
+        assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isFalse();
+        assertThat(l.getLatitude()).isEqualTo(0);
+        assertThat(l.getLongitude()).isEqualTo(0);
+        assertThat(l.hasAltitude()).isFalse();
+        assertThat(l.hasSpeed()).isFalse();
+        assertThat(l.hasBearing()).isFalse();
+        assertThat(l.hasVerticalAccuracy()).isFalse();
+        assertThat(l.hasSpeedAccuracy()).isFalse();
+        assertThat(l.hasBearingAccuracy()).isFalse();
+        assertThat(l.isMock()).isFalse();
+        assertThat(l.getExtras()).isNull();
+    }
 
-        try {
-            new Location((Location) null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
+    @Test
+    public void testSet() {
+        Location location = new Location("");
+
+        Location l = new Location("test");
+        l.setTime(1);
+        l.setElapsedRealtimeNanos(2);
+        l.setElapsedRealtimeUncertaintyNanos(3);
+        l.setLatitude(-90);
+        l.setLongitude(90);
+        l.setAltitude(100);
+        l.setVerticalAccuracyMeters(90);
+        l.setSpeed(1000);
+        l.setSpeedAccuracyMetersPerSecond(9);
+        l.setBearing(7);
+        l.setBearingAccuracyDegrees(11);
+        l.setMock(true);
+        Bundle b = new Bundle();
+        b.putString("key", "value");
+        l.setExtras(b);
+
+        location.set(l);
+        assertThat(location).isEqualTo(l);
+    }
+
+    @Test
+    public void testValues() {
+        Location l = new Location("provider");
+
+        l.setProvider("test");
+        assertThat(l.getProvider()).isEqualTo("test");
+
+        l.setTime(1);
+        assertThat(l.getTime()).isEqualTo(1);
+        l.setTime(Long.MAX_VALUE);
+        assertThat(l.getTime()).isEqualTo(Long.MAX_VALUE);
+
+        l.setElapsedRealtimeNanos(1);
+        assertThat(l.getElapsedRealtimeNanos()).isEqualTo(1);
+        l.setElapsedRealtimeNanos(Long.MAX_VALUE);
+        assertThat(l.getElapsedRealtimeNanos()).isEqualTo(Long.MAX_VALUE);
+
+        l.setElapsedRealtimeUncertaintyNanos(1);
+        assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isTrue();
+        assertThat(l.getElapsedRealtimeUncertaintyNanos()).isEqualTo(1);
+        l.removeElapsedRealtimeUncertaintyNanos();
+        assertThat(l.hasElapsedRealtimeUncertaintyNanos()).isFalse();
+
+        l.setLatitude(-90);
+        assertThat(l.getLatitude()).isEqualTo(-90);
+
+        l.setLongitude(90);
+        assertThat(l.getLongitude()).isEqualTo(90);
+
+        l.setAltitude(100);
+        assertThat(l.hasAltitude()).isTrue();
+        assertThat(l.getAltitude()).isEqualTo(100);
+        l.removeAltitude();
+        assertThat(l.hasAltitude()).isFalse();
+
+        l.setVerticalAccuracyMeters(90);
+        assertThat(l.hasVerticalAccuracy()).isTrue();
+        assertThat(l.getVerticalAccuracyMeters()).isEqualTo(90);
+        l.removeVerticalAccuracy();
+        assertThat(l.hasVerticalAccuracy()).isFalse();
+
+        l.setSpeed(1000);
+        assertThat(l.hasSpeed()).isTrue();
+        assertThat(l.getSpeed()).isEqualTo(1000);
+        l.removeSpeed();
+        assertThat(l.hasSpeed()).isFalse();
+
+        l.setSpeedAccuracyMetersPerSecond(9);
+        assertThat(l.hasSpeedAccuracy()).isTrue();
+        assertThat(l.getSpeedAccuracyMetersPerSecond()).isEqualTo(9);
+        l.removeSpeedAccuracy();
+        assertThat(l.hasSpeedAccuracy()).isFalse();
+
+        l.setBearing(7);
+        assertThat(l.hasBearing()).isTrue();
+        assertThat(l.getBearing()).isEqualTo(7);
+        l.setBearing(Float.MAX_VALUE);
+        assertThat(l.getBearing()).isEqualTo(0);
+        l.setBearing((Float.MAX_VALUE - 1) * -1);
+        assertThat(l.getBearing()).isEqualTo(0);
+        l.setBearing(371);
+        assertThat(l.getBearing()).isEqualTo(11f);
+        l.setBearing(-371);
+        assertThat(l.getBearing()).isEqualTo(349f);
+        l.removeBearing();
+        assertThat(l.hasBearing()).isFalse();
+
+        l.setBearingAccuracyDegrees(11);
+        assertThat(l.hasBearingAccuracy()).isTrue();
+        assertThat(l.getBearingAccuracyDegrees()).isEqualTo(11);
+        l.removeBearingAccuracy();
+        assertThat(l.hasBearingAccuracy()).isFalse();
+
+        l.setMock(true);
+        assertThat(l.isMock()).isTrue();
+        l.setMock(false);
+        assertThat(l.isMock()).isFalse();
+
+        l.setExtras(new Bundle());
+        assertThat(l.getExtras()).isNotNull();
     }
 
     @Test
     public void testDump() {
         StringBuilder sb = new StringBuilder();
-        StringBuilderPrinter printer = new StringBuilderPrinter(sb);
-        Location location = new Location("LocationProvider");
-        location.dump(printer, "");
+        new Location("").dump(new StringBuilderPrinter(sb), "");
         assertNotNull(sb.toString());
     }
 
     @Test
-    public void testBearingTo() {
-        Location location = new Location("");
-        Location dest = new Location("");
-
-        // set the location to Beijing
-        location.setLatitude(39.9);
-        location.setLongitude(116.4);
-        // set the destination to Chengdu
-        dest.setLatitude(30.7);
-        dest.setLongitude(104.1);
-        assertEquals(-128.66, location.bearingTo(dest), DELTA);
-
-        float bearing;
-        Location zeroLocation = new Location("");
-        zeroLocation.setLatitude(0);
-        zeroLocation.setLongitude(0);
-
-        Location testLocation = new Location("");
-        testLocation.setLatitude(0);
-        testLocation.setLongitude(150);
-
-        bearing = zeroLocation.bearingTo(zeroLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(90.0f, bearing, DELTA);
-
-        testLocation.setLatitude(90);
-        testLocation.setLongitude(0);
-        bearing = zeroLocation.bearingTo(testLocation);
-        assertEquals(0.0f, bearing, DELTA);
-
-        try {
-            location.bearingTo(null);
-            fail("should throw NullPointerException");
-        } catch (NullPointerException e) {
-            // expected.
-        }
-    }
-
-    @Test
     public void testConvert_CoordinateToRepresentation() {
         DecimalFormat df = new DecimalFormat("###.#####");
         String result;
@@ -219,12 +271,6 @@
     }
 
     @Test
-    public void testDescribeContents() {
-        Location location = new Location("");
-        location.describeContents();
-    }
-
-    @Test
     public void testDistanceBetween() {
         float[] result = new float[3];
         Location.distanceBetween(0, 0, 0, 0, result);
@@ -271,278 +317,72 @@
     }
 
     @Test
-    public void testAccessAccuracy() {
+    public void testBearingTo() {
         Location location = new Location("");
-        assertFalse(location.hasAccuracy());
+        Location dest = new Location("");
 
-        location.setAccuracy(1.0f);
-        assertEquals(1.0, location.getAccuracy(), DELTA);
-        assertTrue(location.hasAccuracy());
+        // set the location to Beijing
+        location.setLatitude(39.9);
+        location.setLongitude(116.4);
+        // set the destination to Chengdu
+        dest.setLatitude(30.7);
+        dest.setLongitude(104.1);
+        assertEquals(-128.66, location.bearingTo(dest), DELTA);
+
+        float bearing;
+        Location zeroLocation = new Location("");
+        zeroLocation.setLatitude(0);
+        zeroLocation.setLongitude(0);
+
+        Location testLocation = new Location("");
+        testLocation.setLatitude(0);
+        testLocation.setLongitude(150);
+
+        bearing = zeroLocation.bearingTo(zeroLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(90.0f, bearing, DELTA);
+
+        testLocation.setLatitude(90);
+        testLocation.setLongitude(0);
+        bearing = zeroLocation.bearingTo(testLocation);
+        assertEquals(0.0f, bearing, DELTA);
+
+        try {
+            location.bearingTo(null);
+            fail("should throw NullPointerException");
+        } catch (NullPointerException e) {
+            // expected.
+        }
     }
 
     @Test
-    public void testAccessVerticalAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasVerticalAccuracy());
-
-        location.setVerticalAccuracyMeters(1.0f);
-        assertEquals(1.0, location.getVerticalAccuracyMeters(), DELTA);
-        assertTrue(location.hasVerticalAccuracy());
-    }
-
-    @Test
-    public void testAccessSpeedAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeedAccuracy());
-
-        location.setSpeedAccuracyMetersPerSecond(1.0f);
-        assertEquals(1.0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertTrue(location.hasSpeedAccuracy());
-    }
-
-    @Test
-    public void testAccessBearingAccuracy() {
-        Location location = new Location("");
-        assertFalse(location.hasBearingAccuracy());
-
-        location.setBearingAccuracyDegrees(1.0f);
-        assertEquals(1.0, location.getBearingAccuracyDegrees(), DELTA);
-        assertTrue(location.hasBearingAccuracy());
-    }
-
-
-    @Test
-    public void testAccessAltitude() {
-        Location location = new Location("");
-        assertFalse(location.hasAltitude());
-
-        location.setAltitude(1.0);
-        assertEquals(1.0, location.getAltitude(), DELTA);
-        assertTrue(location.hasAltitude());
-    }
-
-    @Test
-    public void testAccessBearing() {
-        Location location = new Location("");
-        assertFalse(location.hasBearing());
-
-        location.setBearing(1.0f);
-        assertEquals(1.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(371.0f);
-        assertEquals(11.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-
-        location.setBearing(-361.0f);
-        assertEquals(359.0, location.getBearing(), DELTA);
-        assertTrue(location.hasBearing());
-    }
-
-    @Test
-    public void testAccessExtras() {
-        Location location = createTestLocation();
-
-        assertTestBundle(location.getExtras());
-
-        location.setExtras(null);
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testAccessLatitude() {
-        Location location = new Location("");
-
-        location.setLatitude(0);
-        assertEquals(0, location.getLatitude(), DELTA);
-
-        location.setLatitude(90);
-        assertEquals(90, location.getLatitude(), DELTA);
-
-        location.setLatitude(-90);
-        assertEquals(-90, location.getLatitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessLongitude() {
-        Location location = new Location("");
-
-        location.setLongitude(0);
-        assertEquals(0, location.getLongitude(), DELTA);
-
-        location.setLongitude(180);
-        assertEquals(180, location.getLongitude(), DELTA);
-
-        location.setLongitude(-180);
-        assertEquals(-180, location.getLongitude(), DELTA);
-    }
-
-    @Test
-    public void testAccessProvider() {
-        Location location = new Location("");
-
-        String provider = "Location Provider";
-        location.setProvider(provider);
-        assertEquals(provider, location.getProvider());
-
-        location.setProvider(null);
-        assertNull(location.getProvider());
-    }
-
-    @Test
-    public void testAccessSpeed() {
-        Location location = new Location("");
-        assertFalse(location.hasSpeed());
-
-        location.setSpeed(234.0045f);
-        assertEquals(234.0045, location.getSpeed(), DELTA);
-        assertTrue(location.hasSpeed());
-    }
-
-    @Test
-    public void testAccessTime() {
-        Location location = new Location("");
-
-        location.setTime(0);
-        assertEquals(0, location.getTime());
-
-        location.setTime(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getTime());
-
-        location.setTime(12000);
-        assertEquals(12000, location.getTime());
-    }
-
-    @Test
-    public void testAccessElapsedRealtime() {
-        Location location = new Location("");
-
-        location.setElapsedRealtimeNanos(0);
-        assertEquals(0, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(Long.MAX_VALUE);
-        assertEquals(Long.MAX_VALUE, location.getElapsedRealtimeNanos());
-
-        location.setElapsedRealtimeNanos(12000);
-        assertEquals(12000, location.getElapsedRealtimeNanos());
-    }
-
-    @Test
-    public void testAccessElapsedRealtimeUncertaintyNanos() {
-        Location location = new Location("");
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-
-        location.setElapsedRealtimeUncertaintyNanos(12000.0);
-        assertEquals(12000.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-        assertTrue(location.hasElapsedRealtimeUncertaintyNanos());
-
-        location.reset();
-        assertFalse(location.hasElapsedRealtimeUncertaintyNanos());
-        assertEquals(0.0, location.getElapsedRealtimeUncertaintyNanos(), DELTA);
-    }
-
-    @Test
-    public void testSetMock() {
-        Location location = new Location("");
-        assertFalse(location.isMock());
-        location.setMock(true);
-        assertTrue(location.isMock());
-    }
-
-    @Test
-    public void testSet() {
-        Location location = new Location("");
-
-        Location loc = createTestLocation();
-
-        location.set(loc);
-        assertTestLocation(location);
-
-        location.reset();
-        assertNull(location.getProvider());
-        assertEquals(0, location.getTime());
-        assertEquals(0, location.getLatitude(), DELTA);
-        assertEquals(0, location.getLongitude(), DELTA);
-        assertEquals(0, location.getAltitude(), DELTA);
-        assertFalse(location.hasAltitude());
-        assertEquals(0, location.getSpeed(), DELTA);
-        assertFalse(location.hasSpeed());
-        assertEquals(0, location.getBearing(), DELTA);
-        assertFalse(location.hasBearing());
-        assertEquals(0, location.getAccuracy(), DELTA);
-        assertFalse(location.hasAccuracy());
-
-        assertEquals(0, location.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(0, location.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(0, location.getBearingAccuracyDegrees(), DELTA);
-
-        assertFalse(location.hasVerticalAccuracy());
-        assertFalse(location.hasSpeedAccuracy());
-        assertFalse(location.hasBearingAccuracy());
-
-        assertNull(location.getExtras());
-    }
-
-    @Test
-    public void testToString() {
-        Location location = createTestLocation();
-
-        assertNotNull(location.toString());
-    }
-
-    @Test
-    public void testWriteToParcel() {
-        Location location = createTestLocation();
+    public void testParcelRoundtrip() {
+        Location l = new Location("test");
+        l.setTime(1);
+        l.setElapsedRealtimeNanos(2);
+        l.setElapsedRealtimeUncertaintyNanos(3);
+        l.setLatitude(-90);
+        l.setLongitude(90);
+        l.setAltitude(100);
+        l.setVerticalAccuracyMeters(90);
+        l.setSpeed(1000);
+        l.setSpeedAccuracyMetersPerSecond(9);
+        l.setBearing(7);
+        l.setBearingAccuracyDegrees(11);
+        l.setMock(true);
+        Bundle b = new Bundle();
+        b.putString("key", "value");
+        l.setExtras(b);
 
         Parcel parcel = Parcel.obtain();
-        location.writeToParcel(parcel, 0);
-        parcel.setDataPosition(0);
-        Location newLocation = Location.CREATOR.createFromParcel(parcel);
-        assertTestLocation(newLocation);
-
-        parcel.recycle();
-    }
-
-    private void assertTestLocation(Location l) {
-        assertNotNull(l);
-        assertEquals(TEST_PROVIDER, l.getProvider());
-        assertEquals(TEST_ACCURACY, l.getAccuracy(), DELTA);
-        assertEquals(TEST_VERTICAL_ACCURACY, l.getVerticalAccuracyMeters(), DELTA);
-        assertEquals(TEST_SPEED_ACCURACY, l.getSpeedAccuracyMetersPerSecond(), DELTA);
-        assertEquals(TEST_BEARING_ACCURACY, l.getBearingAccuracyDegrees(), DELTA);
-        assertEquals(TEST_ALTITUDE, l.getAltitude(), DELTA);
-        assertEquals(TEST_LATITUDE, l.getLatitude(), DELTA);
-        assertEquals(TEST_BEARING, l.getBearing(), DELTA);
-        assertEquals(TEST_LONGITUDE, l.getLongitude(), DELTA);
-        assertEquals(TEST_SPEED, l.getSpeed(), DELTA);
-        assertEquals(TEST_TIME, l.getTime());
-        assertTestBundle(l.getExtras());
-    }
-
-    private Location createTestLocation() {
-        Location l = new Location(TEST_PROVIDER);
-        l.setAccuracy(TEST_ACCURACY);
-        l.setVerticalAccuracyMeters(TEST_VERTICAL_ACCURACY);
-        l.setSpeedAccuracyMetersPerSecond(TEST_SPEED_ACCURACY);
-        l.setBearingAccuracyDegrees(TEST_BEARING_ACCURACY);
-
-        l.setAltitude(TEST_ALTITUDE);
-        l.setLatitude(TEST_LATITUDE);
-        l.setBearing(TEST_BEARING);
-        l.setLongitude(TEST_LONGITUDE);
-        l.setSpeed(TEST_SPEED);
-        l.setTime(TEST_TIME);
-        Bundle bundle = new Bundle();
-        bundle.putBoolean(TEST_KEY1NAME, TEST_KEY1VALUE);
-        bundle.putByte(TEST_KEY2NAME, TEST_KEY2VALUE);
-        l.setExtras(bundle);
-
-        return l;
-    }
-
-    private void assertTestBundle(Bundle bundle) {
-        assertFalse(bundle.getBoolean(TEST_KEY1NAME));
-        assertEquals(TEST_KEY2VALUE, bundle.getByte(TEST_KEY2NAME));
+        try {
+            l.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            assertThat(Location.CREATOR.createFromParcel(parcel)).isEqualTo(l);
+        } finally {
+            parcel.recycle();
+        }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index 10bfb68..33c6fdb 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -481,7 +481,7 @@
         private native boolean nativeTestGetTrackFormat(String srcPath, String outPath,
                 int outFormat);
 
-        private void verifyLocationInFile(String fileName) {
+        private void verifyLocationInFile(String fileName) throws IOException {
             if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 &&
                     mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return;
             MediaMetadataRetriever retriever = new MediaMetadataRetriever();
@@ -514,7 +514,7 @@
             retriever.release();
         }
 
-        private void verifyOrientation(String fileName) {
+        private void verifyOrientation(String fileName) throws IOException {
             if (mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 &&
                     mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) return;
             MediaMetadataRetriever retriever = new MediaMetadataRetriever();
@@ -695,14 +695,14 @@
         }
 
         @Test
-        public void testSetLocationNative() {
+        public void testSetLocationNative() throws IOException {
             Assume.assumeTrue(shouldRunTest(mOutFormat));
             assertTrue(nativeTestSetLocation(mOutFormat, mInpPath, mOutPath));
             verifyLocationInFile(mOutPath);
         }
 
         @Test
-        public void testSetOrientationHintNative() {
+        public void testSetOrientationHintNative() throws IOException {
             Assume.assumeTrue(shouldRunTest(mOutFormat));
             assertTrue(nativeTestSetOrientationHint(mOutFormat, mInpPath, mOutPath));
             verifyOrientation(mOutPath);
diff --git a/tests/mediapc/OWNERS b/tests/mediapc/OWNERS
index 091c4df..d61c4a2 100644
--- a/tests/mediapc/OWNERS
+++ b/tests/mediapc/OWNERS
@@ -1,7 +1,6 @@
 # Bug component: 1344
 # include media developers and framework video team
 include platform/frameworks/av:/media/OWNERS
-chz@google.com
 dichenzhang@google.com
 essick@google.com
 gokrishnan@google.com
diff --git a/tests/process/Android.bp b/tests/process/Android.bp
new file mode 100644
index 0000000..a70625f
--- /dev/null
+++ b/tests/process/Android.bp
@@ -0,0 +1,101 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// We build the main test APK and helper APKs from the same source code,
+// just so they can easily share the constants, etc.
+
+java_library {
+    name: "CtsProcessTestCore",
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "guava",
+        "truth-prebuilt",
+        "testng",
+    ],
+    libs: ["android.test.base"],
+    srcs: [
+        "src/**/*.java",
+    ],
+}
+
+java_defaults {
+    name: "CtsProcessTest_default",
+    static_libs: [
+        "CtsProcessTestCore",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
+
+android_test {
+    name: "CtsProcessTest",
+    defaults: [
+        "cts_defaults",
+        "CtsProcessTest_default",
+    ],
+    manifest: "AndroidManifest_main.xml",
+}
+
+android_test_helper_app {
+    name: "CtsProcessTestHelper1",
+    defaults: ["CtsProcessTest_default"],
+
+    manifest: "AndroidManifest_helper1.xml",
+    additional_manifests: [
+        "AndroidManifest_helper.xml",
+    ],
+    package_name: "android.os.cts.process.helper1",
+}
+
+android_test_helper_app {
+    name: "CtsProcessTestHelper2",
+    defaults: ["CtsProcessTest_default"],
+
+    manifest: "AndroidManifest_helper2.xml",
+    additional_manifests: [
+        "AndroidManifest_helper.xml",
+    ],
+    package_name: "android.os.cts.process.helper2",
+}
+
+android_test_helper_app {
+    name: "CtsProcessTestHelper3",
+    defaults: ["CtsProcessTest_default"],
+
+    manifest: "AndroidManifest_helper3.xml",
+    additional_manifests: [
+        "AndroidManifest_helper.xml",
+    ],
+    package_name: "android.os.cts.process.helper3",
+}
+
+android_test_helper_app {
+    name: "CtsProcessTestHelper4",
+    defaults: ["CtsProcessTest_default"],
+
+    manifest: "AndroidManifest_helper4.xml",
+    additional_manifests: [
+        "AndroidManifest_helper.xml",
+    ],
+    package_name: "android.os.cts.process.helper4",
+}
diff --git a/tests/process/AndroidManifest_helper.xml b/tests/process/AndroidManifest_helper.xml
new file mode 100644
index 0000000..45ac951
--- /dev/null
+++ b/tests/process/AndroidManifest_helper.xml
@@ -0,0 +1,42 @@
+<?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.os.cts.process.helper" >
+
+    <application>
+        <receiver android:name=".MyReceiver0"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="ACTION_SEND_BACK_START_TIME" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".MyReceiver1"
+            android:exported="true"
+            android:process=":sub1">
+            <intent-filter>
+                <action android:name="ACTION_SEND_BACK_START_TIME" />
+            </intent-filter>
+        </receiver>
+        <receiver android:name=".MyReceiver2"
+            android:exported="true"
+            android:process=":sub2">
+            <intent-filter>
+                <action android:name="ACTION_SEND_BACK_START_TIME" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper1.xml b/tests/process/AndroidManifest_helper1.xml
new file mode 100644
index 0000000..b59ea1b
--- /dev/null
+++ b/tests/process/AndroidManifest_helper1.xml
@@ -0,0 +1,27 @@
+<?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.os.cts.process.helper" >
+
+    <application>
+        <processes>
+            <process /> <!-- For the main process -->
+            <process android:process=":sub1" android:name=".Application1" />
+            <process android:process=":sub2"/>
+        </processes>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper2.xml b/tests/process/AndroidManifest_helper2.xml
new file mode 100644
index 0000000..89346bd
--- /dev/null
+++ b/tests/process/AndroidManifest_helper2.xml
@@ -0,0 +1,27 @@
+<?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.os.cts.process.helper" >
+
+    <application android:name=".Application1" >
+        <processes>
+            <process /> <!-- For the main process -->
+            <process android:process=":sub1" android:name=".Application1b" />
+            <process android:process=":sub2" android:name=".Application2b" />
+        </processes>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper3.xml b/tests/process/AndroidManifest_helper3.xml
new file mode 100644
index 0000000..9b9ef69
--- /dev/null
+++ b/tests/process/AndroidManifest_helper3.xml
@@ -0,0 +1,36 @@
+<?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.os.cts.process.helper"
+    android:sharedUserId="android.os.cts.process.helper.shared">
+
+    <application android:process="android.os.cts.process.helper.shared_process">
+        <processes>
+            <process android:process="android.os.cts.process.helper.shared_process" />
+            <process android:process=":sub1" android:name=".Application1c" />
+            <process android:process=":sub2"/>
+            <process android:process="android.os.cts.process.helper.shared.sub3" android:name=".Application3" />
+        </processes>
+        <receiver android:name=".MyReceiver3"
+            android:exported="true"
+            android:process="android.os.cts.process.helper.shared.sub3">
+            <intent-filter>
+                <action android:name="ACTION_SEND_BACK_START_TIME" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_helper4.xml b/tests/process/AndroidManifest_helper4.xml
new file mode 100644
index 0000000..508dae2
--- /dev/null
+++ b/tests/process/AndroidManifest_helper4.xml
@@ -0,0 +1,37 @@
+<?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.os.cts.process.helper"
+    android:sharedUserId="android.os.cts.process.helper.shared">
+
+    <application android:process="android.os.cts.process.helper.shared_process"
+            android:name=".Application1" >
+        <processes>
+            <process android:process="android.os.cts.process.helper.shared_process" />
+            <process android:process=":sub1"/>
+            <process android:process=":sub2" android:name=".Application2" />
+            <process android:process="android.os.cts.process.helper.shared.sub3" android:name=".Application3b" />
+        </processes>
+        <receiver android:name=".MyReceiver3"
+            android:exported="true"
+            android:process="android.os.cts.process.helper.shared.sub3">
+            <intent-filter>
+                <action android:name="ACTION_SEND_BACK_START_TIME" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidManifest_main.xml b/tests/process/AndroidManifest_main.xml
new file mode 100644
index 0000000..3ce892e
--- /dev/null
+++ b/tests/process/AndroidManifest_main.xml
@@ -0,0 +1,26 @@
+<?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.os.cts.process" >
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.os.cts.process" />
+</manifest>
\ No newline at end of file
diff --git a/tests/process/AndroidTest.xml b/tests/process/AndroidTest.xml
new file mode 100644
index 0000000..e3e9897
--- /dev/null
+++ b/tests/process/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?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 BlobStore test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsProcessTest.apk" />
+        <option name="test-file-name" value="CtsProcessTestHelper1.apk" />
+        <option name="test-file-name" value="CtsProcessTestHelper2.apk" />
+        <option name="test-file-name" value="CtsProcessTestHelper3.apk" />
+        <option name="test-file-name" value="CtsProcessTestHelper4.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="am wait-for-broadcast-idle" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.os.cts.process" />
+    </test>
+</configuration>
diff --git a/tests/process/OWNERS b/tests/process/OWNERS
new file mode 100644
index 0000000..3b15c95
--- /dev/null
+++ b/tests/process/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 316234
+include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
diff --git a/tests/process/src/android/os/cts/process/ProcessTest2.java b/tests/process/src/android/os/cts/process/ProcessTest2.java
new file mode 100644
index 0000000..c45577e
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/ProcessTest2.java
@@ -0,0 +1,305 @@
+/*
+ * 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.os.cts.process;
+
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER1_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER2_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER3_RECEIVER3;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER0;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER1;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER2;
+import static android.os.cts.process.common.Consts.HELPER4_RECEIVER3;
+import static android.os.cts.process.common.Consts.HELPER_SHARED_PROCESS_NAME;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER1;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER2;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER3;
+import static android.os.cts.process.common.Consts.PACKAGE_HELPER4;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.Process;
+import android.os.SystemClock;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.Printer;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner;
+
+import com.android.compatibility.common.util.BroadcastMessenger.Receiver;
+import com.android.compatibility.common.util.ShellUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * CTS for {@link android.os.Process}.
+ *
+ * We have more test in cts/tests/tests/os too.
+ */
+@RunWith(AndroidJUnit4ClassRunner.class)
+public class ProcessTest2 {
+    protected static final Context sContext = InstrumentationRegistry.getTargetContext();
+
+    /** Tell all the helper app processes to stop */
+    private static void stopAllHelperApps() throws Exception {
+        // Make sure all the broadcasts are delivered.
+        ShellUtils.runShellCommand("am wait-for-broadcast-idle");
+        Thread.sleep(500); // Just give the system a bit time to breathe.
+        ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER1);
+        ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER2);
+        ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER3);
+        ShellUtils.runShellCommand("am force-stop " + PACKAGE_HELPER4);
+        Thread.sleep(500); // Just give the system a bit time to breathe.
+    }
+
+    public void checkStartTime(ComponentName cn, String expectedProcessName) throws Exception {
+        // Start the target process by sending a broadcast, and get back the results
+        // from the target APIs.
+        try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+            // Start the first process.
+            Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+                    .setComponent(cn)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            final long beforeStartElapsedRealtime = SystemClock.elapsedRealtime();
+            final long beforeStartUptimeMillis = SystemClock.uptimeMillis();
+
+            sContext.sendBroadcast(intent);
+
+            final Message m = receiver.waitForNextMessage();
+
+            // Check the start times.
+            assertThat(m.startRequestedElapsedRealtime).isAtLeast(beforeStartElapsedRealtime);
+            assertThat(m.startElapsedRealtime).isAtLeast(m.startRequestedElapsedRealtime);
+
+            assertThat(m.startRequestedUptimeMillis).isAtLeast(beforeStartUptimeMillis);
+            assertThat(m.startUptimeMillis).isAtLeast(m.startRequestedUptimeMillis);
+
+            // Check the process name.
+            assertThat(m.processName).isEqualTo(expectedProcessName);
+
+            // There may be more message, if the process has a custom app class, but ignore that.
+        }
+    }
+
+    /**
+     * Test for:
+     * {@link Process#getStartElapsedRealtime()}
+     * {@link Process#getStartUptimeMillis()}
+     * {@link Process#getStartRequestedElapsedRealtime()}
+     * {@link Process#getStartRequestedUptimeMillis()}
+     */
+    @Test
+    public void testStartTime() throws Exception {
+        stopAllHelperApps();
+
+        // Main process.
+        checkStartTime(HELPER1_RECEIVER0, PACKAGE_HELPER1);
+
+        // Sub process.
+        checkStartTime(HELPER1_RECEIVER1, PACKAGE_HELPER1 + ":sub1");
+    }
+
+    /**
+     * Test for:
+     * {@link Process#getStartElapsedRealtime()}
+     * {@link Process#getStartUptimeMillis()}
+     * {@link Process#getStartRequestedElapsedRealtime()}
+     * {@link Process#getStartRequestedUptimeMillis()}
+     *
+     * but for a shared process.
+     */
+    @Test
+    public void testStartTime_sharedProcess() throws Exception {
+        stopAllHelperApps();
+
+        try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+            // Bring up the first package on the same process.
+            final Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+                    .setComponent(Consts.HELPER3_RECEIVER0)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            final long beforeStartElapsedRealtime = SystemClock.elapsedRealtime();
+            final long beforeStartUptimeMillis = SystemClock.uptimeMillis();
+
+            sContext.sendBroadcast(intent);
+
+            final Message m = receiver.waitForNextMessage();
+
+            // Check the start times.
+
+            assertThat(m.startRequestedElapsedRealtime).isAtLeast(beforeStartElapsedRealtime);
+            assertThat(m.startElapsedRealtime).isAtLeast(m.startRequestedElapsedRealtime);
+
+            assertThat(m.startRequestedUptimeMillis).isAtLeast(beforeStartUptimeMillis);
+            assertThat(m.startUptimeMillis).isAtLeast(m.startRequestedUptimeMillis);
+
+            // Check the process name.
+            assertThat(m.processName).isEqualTo(HELPER_SHARED_PROCESS_NAME);
+
+            // Bring up the first package on the same process.
+            // The start request time should still be the same as the above result.
+            final Intent intent2 = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+                    .setComponent(Consts.HELPER4_RECEIVER0)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            sContext.sendBroadcast(intent);
+
+            final Message m2 = receiver.waitForNextMessage();
+
+            assertThat(m2.startRequestedElapsedRealtime)
+                    .isEqualTo(m.startRequestedElapsedRealtime);
+            assertThat(m2.startRequestedUptimeMillis)
+                    .isEqualTo(m.startRequestedUptimeMillis);
+
+            assertThat(m2.processName).isEqualTo(HELPER_SHARED_PROCESS_NAME);
+
+            receiver.ensureNoMoreMessages();
+        }
+    }
+
+    private void checkApplicationClass(ComponentName receiverComponent,
+            @NonNull String expectedPackageName, @NonNull String expectedProcessName,
+            @Nullable String expectedApplicationClassName) throws Exception {
+
+        // Start the target process by sending a receiver, and get back the results
+        // from the target APIs.
+        try (Receiver<Message> receiver = new Receiver<>(sContext, Consts.TAG)) {
+
+            // Start the first process.
+            Intent intent = new Intent(Consts.ACTION_SEND_BACK_START_TIME)
+                    .setComponent(receiverComponent)
+                    .setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+
+            sContext.sendBroadcast(intent);
+
+            // If the process has a custom application class, then the first message should be from
+            // the application class.
+            if (expectedApplicationClassName != null) {
+                final Message m = receiver.waitForNextMessage();
+                Log.i(Consts.TAG, "Message (which is supposed to be from the application class): "
+                        + m);
+
+                assertThat(m.packageName).isEqualTo(expectedPackageName);
+                assertThat(m.processName).isEqualTo(expectedProcessName);
+                assertThat(m.applicationClassName).isEqualTo(expectedApplicationClassName);
+            }
+
+            // Then there should be a message from the receiver.
+            final Message m = receiver.waitForNextMessage();
+            Log.i(Consts.TAG, "Message (which is supposed to be from the receiver): " + m);
+
+            assertThat(m.packageName).isEqualTo(expectedPackageName);
+            assertThat(m.processName).isEqualTo(expectedProcessName);
+
+            if (expectedApplicationClassName != null) {
+                assertThat(m.applicationContextClassName).isEqualTo(expectedApplicationClassName);
+            } else {
+                // No custom app class, so the default app class should be used.
+                assertThat(m.applicationContextClassName)
+                        .isEqualTo(android.app.Application.class.getCanonicalName());
+            }
+
+            receiver.ensureNoMoreMessages();
+        }
+    }
+
+    /**
+     * Make sure the correct app class is instantiated in the app processes.
+     */
+    @Test
+    public void testApplicationClass() throws Exception {
+        stopAllHelperApps();
+
+        // Each receiver in each helper package runs on different processes, which may or may
+        // not have a custom application class.
+        checkApplicationClass(HELPER1_RECEIVER0, PACKAGE_HELPER1, PACKAGE_HELPER1,
+                null);
+        checkApplicationClass(HELPER1_RECEIVER1, PACKAGE_HELPER1, PACKAGE_HELPER1 + ":sub1",
+                "android.os.cts.process.helper.Application1");
+        checkApplicationClass(HELPER1_RECEIVER2, PACKAGE_HELPER1, PACKAGE_HELPER1 + ":sub2",
+                null);
+
+        checkApplicationClass(HELPER2_RECEIVER0, PACKAGE_HELPER2, PACKAGE_HELPER2,
+                "android.os.cts.process.helper.Application1");
+        checkApplicationClass(HELPER2_RECEIVER1, PACKAGE_HELPER2, PACKAGE_HELPER2 + ":sub1",
+                "android.os.cts.process.helper.Application1b");
+        checkApplicationClass(HELPER2_RECEIVER2, PACKAGE_HELPER2, PACKAGE_HELPER2 + ":sub2",
+                "android.os.cts.process.helper.Application2b");
+
+        checkApplicationClass(HELPER3_RECEIVER0, PACKAGE_HELPER3,
+                "android.os.cts.process.helper.shared_process",
+                null);
+        checkApplicationClass(HELPER3_RECEIVER1, PACKAGE_HELPER3, PACKAGE_HELPER3 + ":sub1",
+                "android.os.cts.process.helper.Application1c");
+        checkApplicationClass(HELPER3_RECEIVER2, PACKAGE_HELPER3, PACKAGE_HELPER3 + ":sub2",
+                null);
+        checkApplicationClass(HELPER3_RECEIVER3, PACKAGE_HELPER3,
+                "android.os.cts.process.helper.shared.sub3",
+                "android.os.cts.process.helper.Application3");
+
+        checkApplicationClass(HELPER4_RECEIVER0, PACKAGE_HELPER4,
+                "android.os.cts.process.helper.shared_process",
+                "android.os.cts.process.helper.Application1");
+        checkApplicationClass(HELPER4_RECEIVER1, PACKAGE_HELPER4, PACKAGE_HELPER4 + ":sub1",
+                "android.os.cts.process.helper.Application1");
+        checkApplicationClass(HELPER4_RECEIVER2, PACKAGE_HELPER4, PACKAGE_HELPER4 + ":sub2",
+                "android.os.cts.process.helper.Application2");
+        checkApplicationClass(HELPER4_RECEIVER3, PACKAGE_HELPER4,
+                "android.os.cts.process.helper.shared.sub3",
+                "android.os.cts.process.helper.Application3b");
+    }
+
+    /**
+     * This doesn't do any assertions, but just dump the ApplicationInfo's for the helper APKs
+     * on logcat, so if some of the tests fail, we can look at the log and verify the
+     * ApplicationInfo is correct.
+     *
+     * (`dumpsys package` doesn't have a way to dump ApplicationInfo at the moment.)
+     */
+    @Test
+    public void dumpApplicationInfo() throws Exception {
+        LogPrinter pw = new LogPrinter(Log.VERBOSE, Consts.TAG);
+        dumpApplicationInfo(pw, PACKAGE_HELPER1);
+        dumpApplicationInfo(pw, Consts.PACKAGE_HELPER2);
+        dumpApplicationInfo(pw, Consts.PACKAGE_HELPER3);
+        dumpApplicationInfo(pw, Consts.PACKAGE_HELPER4);
+    }
+
+    private void dumpApplicationInfo(Printer pw, String packageName) throws Exception {
+        ApplicationInfo ai = sContext.getPackageManager().getApplicationInfo(packageName, 0);
+        pw.println("Dumping " + packageName);
+        ai.dump(pw, "    ");
+    }
+}
diff --git a/tests/process/src/android/os/cts/process/common/Consts.java b/tests/process/src/android/os/cts/process/common/Consts.java
new file mode 100644
index 0000000..dc7409f
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/common/Consts.java
@@ -0,0 +1,72 @@
+/*
+ * 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.os.cts.process.common;
+
+import android.content.ComponentName;
+import android.os.cts.process.helper.MyReceiver0;
+import android.os.cts.process.helper.MyReceiver1;
+import android.os.cts.process.helper.MyReceiver2;
+import android.os.cts.process.helper.MyReceiver3;
+
+public class Consts {
+    private Consts() {
+    }
+
+    public static final String TAG = "CtsProcessTest";
+
+    public static final String PACKAGE_HELPER1 = "android.os.cts.process.helper1";
+    public static final String PACKAGE_HELPER2 = "android.os.cts.process.helper2";
+    public static final String PACKAGE_HELPER3 = "android.os.cts.process.helper3";
+    public static final String PACKAGE_HELPER4 = "android.os.cts.process.helper4";
+
+    public static final String HELPER_SHARED_PROCESS_NAME =
+            "android.os.cts.process.helper.shared_process";
+
+    private static ComponentName buildReceiver(String packageName, int receiverId) {
+        switch (receiverId) {
+            case 0:
+                return new ComponentName(packageName, MyReceiver0.class.getName());
+            case 1:
+                return new ComponentName(packageName, MyReceiver1.class.getName());
+            case 2:
+                return new ComponentName(packageName, MyReceiver2.class.getName());
+            case 3:
+                return new ComponentName(packageName, MyReceiver3.class.getName());
+            default:
+                throw new RuntimeException("Unsupported ID detected: " + receiverId);
+        }
+    }
+
+    public static final ComponentName HELPER1_RECEIVER0 = buildReceiver(PACKAGE_HELPER1, 0);
+    public static final ComponentName HELPER1_RECEIVER1 = buildReceiver(PACKAGE_HELPER1, 1);
+    public static final ComponentName HELPER1_RECEIVER2 = buildReceiver(PACKAGE_HELPER1, 2);
+
+    public static final ComponentName HELPER2_RECEIVER0 = buildReceiver(PACKAGE_HELPER2, 0);
+    public static final ComponentName HELPER2_RECEIVER1 = buildReceiver(PACKAGE_HELPER2, 1);
+    public static final ComponentName HELPER2_RECEIVER2 = buildReceiver(PACKAGE_HELPER2, 2);
+
+    public static final ComponentName HELPER3_RECEIVER0 = buildReceiver(PACKAGE_HELPER3, 0);
+    public static final ComponentName HELPER3_RECEIVER1 = buildReceiver(PACKAGE_HELPER3, 1);
+    public static final ComponentName HELPER3_RECEIVER2 = buildReceiver(PACKAGE_HELPER3, 2);
+    public static final ComponentName HELPER3_RECEIVER3 = buildReceiver(PACKAGE_HELPER3, 3);
+
+    public static final ComponentName HELPER4_RECEIVER0 = buildReceiver(PACKAGE_HELPER4, 0);
+    public static final ComponentName HELPER4_RECEIVER1 = buildReceiver(PACKAGE_HELPER4, 1);
+    public static final ComponentName HELPER4_RECEIVER2 = buildReceiver(PACKAGE_HELPER4, 2);
+    public static final ComponentName HELPER4_RECEIVER3 = buildReceiver(PACKAGE_HELPER4, 3);
+
+    public static final String ACTION_SEND_BACK_START_TIME = "ACTION_SEND_BACK_START_TIME";
+}
diff --git a/tests/process/src/android/os/cts/process/common/Message.java b/tests/process/src/android/os/cts/process/common/Message.java
new file mode 100644
index 0000000..f7b7773
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/common/Message.java
@@ -0,0 +1,198 @@
+/*
+ * 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.os.cts.process.common;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.SystemClock;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * codegen ${ANDROID_BUILD_TOP}/cts/tests/process/src/android/os/cts/process/common/Message.java
+ */
+@DataClass(
+        genConstructor = false,
+        genSetters = false,
+        genToString = true,
+        genAidl = false)
+public class Message implements Parcelable {
+    public Message() {
+    }
+
+    public void fillInBasicInfo(Context context) {
+        // codegen fails for whatever reason if it's after the fields.
+        packageName = context.getPackageName();
+        processName = Process.myProcessName();
+
+        applicationContextClassName = context.getApplicationContext().getClass().getCanonicalName();
+
+        nowElapsedRealtime = SystemClock.elapsedRealtime();
+        nowUptimeMillis = SystemClock.uptimeMillis();
+
+        startElapsedRealtime = Process.getStartElapsedRealtime();
+        startUptimeMillis = Process.getStartUptimeMillis();
+        startRequestedElapsedRealtime = Process.getStartRequestedElapsedRealtime();
+        startRequestedUptimeMillis = Process.getStartRequestedUptimeMillis();
+    }
+
+    @Nullable
+    public String packageName;
+    @Nullable
+    public String applicationClassName;
+    @Nullable
+    public String receiverClassName;
+    @Nullable
+    public String applicationContextClassName;
+    @Nullable
+    public String processName;
+    public long startElapsedRealtime;
+    public long startUptimeMillis;
+    public long startRequestedElapsedRealtime;
+    public long startRequestedUptimeMillis;
+    public long nowElapsedRealtime;
+    public long nowUptimeMillis;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/cts/tests/process/src/android/os/cts/process/common/Message.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "Message { " +
+                "packageName = " + packageName + ", " +
+                "applicationClassName = " + applicationClassName + ", " +
+                "receiverClassName = " + receiverClassName + ", " +
+                "applicationContextClassName = " + applicationContextClassName + ", " +
+                "processName = " + processName + ", " +
+                "startElapsedRealtime = " + startElapsedRealtime + ", " +
+                "startUptimeMillis = " + startUptimeMillis + ", " +
+                "startRequestedElapsedRealtime = " + startRequestedElapsedRealtime + ", " +
+                "startRequestedUptimeMillis = " + startRequestedUptimeMillis + ", " +
+                "nowElapsedRealtime = " + nowElapsedRealtime + ", " +
+                "nowUptimeMillis = " + nowUptimeMillis +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+        // You can override field parcelling by defining methods like:
+        // void parcelFieldName(Parcel dest, int flags) { ... }
+
+        int flg = 0;
+        if (packageName != null) flg |= 0x1;
+        if (applicationClassName != null) flg |= 0x2;
+        if (receiverClassName != null) flg |= 0x4;
+        if (applicationContextClassName != null) flg |= 0x8;
+        if (processName != null) flg |= 0x10;
+        dest.writeInt(flg);
+        if (packageName != null) dest.writeString(packageName);
+        if (applicationClassName != null) dest.writeString(applicationClassName);
+        if (receiverClassName != null) dest.writeString(receiverClassName);
+        if (applicationContextClassName != null) dest.writeString(applicationContextClassName);
+        if (processName != null) dest.writeString(processName);
+        dest.writeLong(startElapsedRealtime);
+        dest.writeLong(startUptimeMillis);
+        dest.writeLong(startRequestedElapsedRealtime);
+        dest.writeLong(startRequestedUptimeMillis);
+        dest.writeLong(nowElapsedRealtime);
+        dest.writeLong(nowUptimeMillis);
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int describeContents() { return 0; }
+
+    /** @hide */
+    @SuppressWarnings({"unchecked", "RedundantCast"})
+    @DataClass.Generated.Member
+    protected Message(@android.annotation.NonNull android.os.Parcel in) {
+        // You can override field unparcelling by defining methods like:
+        // static FieldType unparcelFieldName(Parcel in) { ... }
+
+        int flg = in.readInt();
+        String _packageName = (flg & 0x1) == 0 ? null : in.readString();
+        String _applicationClassName = (flg & 0x2) == 0 ? null : in.readString();
+        String _receiverClassName = (flg & 0x4) == 0 ? null : in.readString();
+        String _applicationContextClassName = (flg & 0x8) == 0 ? null : in.readString();
+        String _processName = (flg & 0x10) == 0 ? null : in.readString();
+        long _startElapsedRealtime = in.readLong();
+        long _startUptimeMillis = in.readLong();
+        long _startRequestedElapsedRealtime = in.readLong();
+        long _startRequestedUptimeMillis = in.readLong();
+        long _nowElapsedRealtime = in.readLong();
+        long _nowUptimeMillis = in.readLong();
+
+        this.packageName = _packageName;
+        this.applicationClassName = _applicationClassName;
+        this.receiverClassName = _receiverClassName;
+        this.applicationContextClassName = _applicationContextClassName;
+        this.processName = _processName;
+        this.startElapsedRealtime = _startElapsedRealtime;
+        this.startUptimeMillis = _startUptimeMillis;
+        this.startRequestedElapsedRealtime = _startRequestedElapsedRealtime;
+        this.startRequestedUptimeMillis = _startRequestedUptimeMillis;
+        this.nowElapsedRealtime = _nowElapsedRealtime;
+        this.nowUptimeMillis = _nowUptimeMillis;
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public static final @android.annotation.NonNull Parcelable.Creator<Message> CREATOR
+            = new Parcelable.Creator<Message>() {
+        @Override
+        public Message[] newArray(int size) {
+            return new Message[size];
+        }
+
+        @Override
+        public Message createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+            return new Message(in);
+        }
+    };
+
+    @DataClass.Generated(
+            time = 1639154672715L,
+            codegenVersion = "1.0.23",
+            sourceFile = "cts/tests/process/src/android/os/cts/process/common/Message.java",
+            inputSignatures = "public @android.annotation.Nullable java.lang.String packageName\npublic @android.annotation.Nullable java.lang.String applicationClassName\npublic @android.annotation.Nullable java.lang.String receiverClassName\npublic @android.annotation.Nullable java.lang.String applicationContextClassName\npublic @android.annotation.Nullable java.lang.String processName\npublic  long startElapsedRealtime\npublic  long startUptimeMillis\npublic  long startRequestedElapsedRealtime\npublic  long startRequestedUptimeMillis\npublic  long nowElapsedRealtime\npublic  long nowUptimeMillis\npublic  void fillInBasicInfo(android.content.Context)\nclass Message extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genSetters=false, genToString=true, genAidl=false)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application0.java b/tests/process/src/android/os/cts/process/helper/Application0.java
new file mode 100644
index 0000000..8c9a860
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application0.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application0 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1.java b/tests/process/src/android/os/cts/process/helper/Application1.java
new file mode 100644
index 0000000..4deae2c
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application1 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1b.java b/tests/process/src/android/os/cts/process/helper/Application1b.java
new file mode 100644
index 0000000..28efd23
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application1b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application1c.java b/tests/process/src/android/os/cts/process/helper/Application1c.java
new file mode 100644
index 0000000..b083e26
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application1c.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application1c extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application2.java b/tests/process/src/android/os/cts/process/helper/Application2.java
new file mode 100644
index 0000000..396ef79
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application2.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application2 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application2b.java b/tests/process/src/android/os/cts/process/helper/Application2b.java
new file mode 100644
index 0000000..c88300f
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application2b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application2b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application3.java b/tests/process/src/android/os/cts/process/helper/Application3.java
new file mode 100644
index 0000000..b238f51
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application3.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application3 extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/Application3b.java b/tests/process/src/android/os/cts/process/helper/Application3b.java
new file mode 100644
index 0000000..ab7eef9
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/Application3b.java
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.process.helper;
+
+public class Application3b extends BaseApplication {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/BaseApplication.java b/tests/process/src/android/os/cts/process/helper/BaseApplication.java
new file mode 100644
index 0000000..a9aa1f9
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/BaseApplication.java
@@ -0,0 +1,44 @@
+/*
+ * 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.os.cts.process.helper;
+
+import android.app.Application;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+
+public abstract class BaseApplication extends Application {
+    @Override
+    public void onCreate() {
+        super.onCreate();
+
+        Log.i(Consts.TAG, "onCreate: this=" + this);
+
+        sendBackApplicationCreated();
+    }
+
+    private void sendBackApplicationCreated() {
+        Message m = new Message();
+
+        m.fillInBasicInfo(this);
+
+        m.applicationClassName = this.getClass().getCanonicalName();
+
+        BroadcastMessenger.send(this, Consts.TAG, m);
+    }
+}
diff --git a/tests/process/src/android/os/cts/process/helper/BaseReceiver.java b/tests/process/src/android/os/cts/process/helper/BaseReceiver.java
new file mode 100644
index 0000000..0e4f8e1
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/BaseReceiver.java
@@ -0,0 +1,47 @@
+/*
+ * 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.os.cts.process.helper;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.cts.process.common.Consts;
+import android.os.cts.process.common.Message;
+import android.util.Log;
+
+import com.android.compatibility.common.util.BroadcastMessenger;
+
+public class BaseReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(Consts.TAG, "onReceive: " + intent);
+        switch (intent.getAction()) {
+            case Consts.ACTION_SEND_BACK_START_TIME:
+                sendBackStartTime(context);
+                break;
+        }
+    }
+
+    private void sendBackStartTime(Context context) {
+        Message m = new Message();
+
+        m.fillInBasicInfo(context);
+
+        m.receiverClassName = this.getClass().getCanonicalName();
+
+        BroadcastMessenger.send(context, Consts.TAG, m);
+    }
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver0.java b/tests/process/src/android/os/cts/process/helper/MyReceiver0.java
new file mode 100644
index 0000000..a5237a2
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver0.java
@@ -0,0 +1,20 @@
+/*
+ * 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.os.cts.process.helper;
+
+public class MyReceiver0 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver1.java b/tests/process/src/android/os/cts/process/helper/MyReceiver1.java
new file mode 100644
index 0000000..a349fab
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver1.java
@@ -0,0 +1,20 @@
+/*
+ * 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.os.cts.process.helper;
+
+public class MyReceiver1 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver2.java b/tests/process/src/android/os/cts/process/helper/MyReceiver2.java
new file mode 100644
index 0000000..d3b41a3
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver2.java
@@ -0,0 +1,20 @@
+/*
+ * 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.os.cts.process.helper;
+
+public class MyReceiver2 extends BaseReceiver {
+}
diff --git a/tests/process/src/android/os/cts/process/helper/MyReceiver3.java b/tests/process/src/android/os/cts/process/helper/MyReceiver3.java
new file mode 100644
index 0000000..075b796
--- /dev/null
+++ b/tests/process/src/android/os/cts/process/helper/MyReceiver3.java
@@ -0,0 +1,20 @@
+/*
+ * 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.os.cts.process.helper;
+
+public class MyReceiver3 extends BaseReceiver {
+}
diff --git a/tests/quicksettings/Android.bp b/tests/quicksettings/Android.bp
new file mode 100644
index 0000000..7246788
--- /dev/null
+++ b/tests/quicksettings/Android.bp
@@ -0,0 +1,43 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsTileServiceTestCases",
+    defaults: ["cts_defaults"],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+     ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.runner",
+        "android.test.base",
+    ],
+
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+}
diff --git a/tests/quicksettings/AndroidManifest.xml b/tests/quicksettings/AndroidManifest.xml
new file mode 100644
index 0000000..225042d
--- /dev/null
+++ b/tests/quicksettings/AndroidManifest.xml
@@ -0,0 +1,49 @@
+<?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.quicksettings.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <service android:name=".TestTileService"
+                 android:exported="true"
+                 android:label="TestTileService"
+                 android:icon="@drawable/robot"
+                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
+            </intent-filter>
+        </service>
+
+        <service android:name=".ToggleableTestTileService"
+                 android:exported="true"
+                 android:label="BooleanTestTileService"
+                 android:icon="@drawable/robot"
+                 android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
+            <intent-filter>
+                <action android:name="android.service.quicksettings.action.QS_TILE"/>
+            </intent-filter>
+            <meta-data android:name="android.service.quicksettings.TOGGLEABLE_TILE"
+                       android:value="true"/>
+        </service>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+         android:targetPackage="android.quicksettings.cts">
+    </instrumentation>
+</manifest>
diff --git a/tests/quicksettings/AndroidTest.xml b/tests/quicksettings/AndroidTest.xml
new file mode 100644
index 0000000..2a2acd9
--- /dev/null
+++ b/tests/quicksettings/AndroidTest.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.
+-->
+
+<configuration description="Config for CtsTileServiceTestCases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsTileServiceTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.quicksettings.cts" />
+    </test>
+</configuration>
diff --git a/tests/quicksettings/OWNERS b/tests/quicksettings/OWNERS
new file mode 100644
index 0000000..43c3658
--- /dev/null
+++ b/tests/quicksettings/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 78930
+kozynski@google.com
+juliacr@google.com
+evanlaird@google.com
\ No newline at end of file
diff --git a/tests/quicksettings/TEST_MAPPING b/tests/quicksettings/TEST_MAPPING
new file mode 100644
index 0000000..8feb0b3
--- /dev/null
+++ b/tests/quicksettings/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTileServiceTestCases"
+    }
+  ]
+}
diff --git a/tests/quicksettings/res/drawable/robot.png b/tests/quicksettings/res/drawable/robot.png
new file mode 100644
index 0000000..8a9e698
--- /dev/null
+++ b/tests/quicksettings/res/drawable/robot.png
Binary files differ
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
new file mode 100644
index 0000000..8ef5807
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.quicksettings.cts;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.service.quicksettings.TileService;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseTileServiceTest {
+
+    protected abstract String getTag();
+    protected abstract String getComponentName();
+    protected abstract TileService getTileServiceInstance();
+    protected abstract void waitForConnected(boolean state) throws InterruptedException;
+    protected abstract void waitForListening(boolean state) throws InterruptedException;
+    protected Context mContext;
+
+    final static String DUMP_COMMAND =
+            "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
+
+    // Time between checks for state we expect.
+    protected static final long CHECK_DELAY = 250;
+    // Number of times to check before failing. This is set so the maximum wait time is about 4s,
+    // as some tests were observed to take around 3s.
+    protected static final long CHECK_RETRIES = 15;
+    // Timeout to wait for launcher
+    protected static final long TIMEOUT = 8000;
+
+    protected TileService mTileService;
+    private Intent homeIntent;
+    private String mLauncherPackage;
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(TileService.isQuickSettingsSupported());
+        mContext = InstrumentationRegistry.getContext();
+        homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+
+        mLauncherPackage = mContext.getPackageManager().resolveActivity(homeIntent,
+                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.packageName;
+
+        // Wait for home
+        UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        device.pressHome();
+        device.wait(Until.hasObject(By.pkg(mLauncherPackage).depth(0)), TIMEOUT);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        expandSettings(false);
+        toggleServiceAccess(getComponentName(), false);
+        waitForConnected(false);
+        assertNull(TestTileService.getInstance());
+    }
+
+    protected void startTileService() throws Exception {
+        toggleServiceAccess(getComponentName(), true);
+        waitForConnected(true); // wait for service to be bound
+        mTileService = getTileServiceInstance();
+        assertNotNull(mTileService);
+    }
+
+    protected void toggleServiceAccess(String componentName, boolean on) throws Exception {
+        String command = " cmd statusbar " + (on ? "add-tile " : "remove-tile ")
+                + componentName;
+
+        executeShellCommand(command);
+    }
+
+    public String executeShellCommand(String command) throws IOException {
+        Log.i(getTag(), "Shell command: " + command);
+        try {
+            return SystemUtil.runShellCommand(getInstrumentation(), command);
+        } catch (IOException e) {
+            //bubble it up
+            Log.e(getTag(), "Error running shell command: " + command);
+            throw new IOException(e);
+        }
+    }
+
+    protected void expandSettings(boolean expand) throws Exception {
+        executeShellCommand(" cmd statusbar " + (expand ? "expand-settings" : "collapse"));
+        Thread.sleep(600); // wait for animation
+    }
+
+    protected void initializeAndListen() throws Exception {
+        startTileService();
+        expandSettings(true);
+        waitForListening(true);
+    }
+
+    /**
+     * Find a line containing {@code label} in {@code lines}.
+     */
+    protected String findLine(String[] lines, CharSequence label) {
+        for (String line: lines) {
+            if (line.contains(label)) {
+                return line;
+            }
+        }
+        return null;
+    }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
new file mode 100644
index 0000000..0f6d56e
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.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 android.quicksettings.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.junit.Test;
+
+public class BooleanTileServiceTest extends BaseTileServiceTest {
+    private final static String TAG = "BooleanTileServiceTest";
+
+    @Test
+    public void testTileIsBoundAndListening() throws Exception {
+        startTileService();
+        expandSettings(true);
+        waitForListening(true);
+    }
+
+    @Test
+    public void testTileInDumpAndHasBooleanState() throws Exception {
+        initializeAndListen();
+
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        final String line = findLine(dumpLines, tileLabel);
+        assertNotNull(line);
+        assertTrue(line.trim().startsWith("BooleanState"));
+    }
+
+    @Test
+    public void testTileStartsInactive() throws Exception {
+        initializeAndListen();
+
+        assertEquals(Tile.STATE_INACTIVE, mTileService.getQsTile().getState());
+    }
+
+    @Test
+    public void testValueTracksState() throws Exception {
+        initializeAndListen();
+
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+        String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        String line = findLine(dumpLines, tileLabel);
+
+        // Tile starts inactive
+        assertTrue(line.contains("value=false"));
+
+        ((ToggleableTestTileService) mTileService).toggleState();
+
+        // Close and open QS to make sure that state is refreshed
+        expandSettings(false);
+        waitForListening(false);
+        expandSettings(true);
+        waitForListening(true);
+
+        assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
+
+        dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        line = findLine(dumpLines, tileLabel);
+
+        assertTrue(line.contains("value=true"));
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected String getComponentName() {
+        return ToggleableTestTileService.getComponentName().flattenToString();
+    }
+
+    @Override
+    protected TileService getTileServiceInstance() {
+        return ToggleableTestTileService.getInstance();
+    }
+
+    /**
+     * Waits for the TileService to be in the expected listening state. If it times out, it fails
+     * the test
+     * @param state desired listening state
+     * @throws InterruptedException
+     */
+    @Override
+    protected void waitForListening(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (ToggleableTestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, ToggleableTestTileService.isListening());
+    }
+
+    /**
+     * Waits for the TileService to be in the expected connected state. If it times out, it fails
+     * the test
+     * @param state desired connected state
+     * @throws InterruptedException
+     */
+    @Override
+    protected void waitForConnected(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (ToggleableTestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, ToggleableTestTileService.isConnected());
+    }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java b/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java
new file mode 100644
index 0000000..0e6d099
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/TestTileService.java
@@ -0,0 +1,103 @@
+/*
+ * 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.quicksettings.cts;
+
+import android.content.ComponentName;
+import android.service.quicksettings.TileService;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class TestTileService extends TileService {
+
+    public static final String TAG = "TestTileService";
+    public static final String PKG = "android.app.stubs";
+    public static final int ICON_ID = R.drawable.robot;
+
+    private static TestTileService sTestTileService = null;
+    AtomicBoolean isConnected = new AtomicBoolean(false);
+    AtomicBoolean isListening = new AtomicBoolean(false);
+    AtomicBoolean hasBeenClicked = new AtomicBoolean(false);
+
+    public static String getId() {
+        return String.format("%s/%s", TestTileService.class.getPackage().getName(),
+                TestTileService.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(TestTileService.class.getPackage().getName(),
+                TestTileService.class.getName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    public static TestTileService getInstance() {
+        return TestTileService.sTestTileService;
+    }
+
+    public void setInstance(TestTileService tile) {
+        sTestTileService = tile;
+    }
+
+    public static boolean isConnected() {
+        return getInstance() != null && getInstance().isConnected.get();
+    }
+
+    public static boolean isListening() {
+        return getInstance().isListening.get();
+    }
+
+    public static boolean hasBeenClicked() {
+        return getInstance().hasBeenClicked.get();
+    }
+
+    @Override
+    public void onStartListening() {
+        super.onStartListening();
+        isListening.set(true);
+    }
+
+    @Override
+    public void onStopListening() {
+        super.onStopListening();
+        isListening.set(false);
+    }
+
+    @Override
+    public void onClick() {
+        super.onClick();
+        hasBeenClicked.set(true);
+    }
+
+    @Override
+    public void onTileAdded() {
+        super.onTileAdded();
+        setInstance(this);
+        isConnected.set(true);
+    }
+
+    @Override
+    public void onTileRemoved() {
+        super.onTileRemoved();
+        setInstance(null);
+        isConnected.set(false);
+        isListening.set(false);
+        hasBeenClicked.set(false);
+    }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
new file mode 100644
index 0000000..2ccab0c
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.quicksettings.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Looper;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+
+import org.junit.Test;
+
+public class TileServiceTest extends BaseTileServiceTest {
+    private final static String TAG = "TileServiceTest";
+
+    @Test
+    public void testCreateTileService() {
+        final TileService tileService = new TileService();
+    }
+
+    @Test
+    public void testListening() throws Exception {
+        initializeAndListen();
+    }
+
+    @Test
+    public void testListening_stopped() throws Exception {
+        initializeAndListen();
+        expandSettings(false);
+        waitForListening(false);
+    }
+
+    @Test
+    public void testLocked_deviceNotLocked() throws Exception {
+        initializeAndListen();
+        assertFalse(mTileService.isLocked());
+    }
+
+    @Test
+    public void testSecure_deviceNotSecure() throws Exception {
+        initializeAndListen();
+        assertFalse(mTileService.isSecure());
+    }
+
+    @Test
+    public void testTile_hasCorrectIcon() throws Exception {
+        initializeAndListen();
+        Tile tile = mTileService.getQsTile();
+        assertEquals(TestTileService.ICON_ID, tile.getIcon().getResId());
+    }
+
+    @Test
+    public void testTile_hasCorrectSubtitle() throws Exception {
+        initializeAndListen();
+
+        Tile tile = mTileService.getQsTile();
+        tile.setSubtitle("test_subtitle");
+        tile.updateTile();
+        assertEquals("test_subtitle", tile.getSubtitle());
+    }
+
+    @Test
+    public void testTile_hasCorrectStateDescription() throws Exception {
+        initializeAndListen();
+
+        Tile tile = mTileService.getQsTile();
+        tile.setStateDescription("test_stateDescription");
+        tile.updateTile();
+        assertEquals("test_stateDescription", tile.getStateDescription());
+    }
+
+    @Test
+    public void testShowDialog() throws Exception {
+        Looper.prepare();
+        Dialog dialog = new AlertDialog.Builder(mContext).create();
+        initializeAndListen();
+        clickTile(TestTileService.getComponentName().flattenToString());
+        waitForClick();
+
+        mTileService.showDialog(dialog);
+
+        assertTrue(dialog.isShowing());
+        dialog.dismiss();
+    }
+
+    @Test
+    public void testUnlockAndRun_phoneIsUnlockedActivityIsRun() throws Exception {
+        initializeAndListen();
+        assertFalse(mTileService.isLocked());
+
+        TestRunnable testRunnable = new TestRunnable();
+
+        mTileService.unlockAndRun(testRunnable);
+        Thread.sleep(100); // wait for activity to run
+        waitForRun(testRunnable);
+    }
+
+    @Test
+    public void testTileInDumpAndHasState() throws Exception {
+        initializeAndListen();
+
+        final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+
+        final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
+        final String line = findLine(dumpLines, tileLabel);
+        assertNotNull(line);
+        assertTrue(line.trim().startsWith("State")); // Not BooleanState
+    }
+
+    private void clickTile(String componentName) throws Exception {
+        executeShellCommand(" cmd statusbar click-tile " + componentName);
+    }
+
+    /**
+     * Waits for the TileService to receive the clicked event. If it times out it fails the test.
+     * @throws InterruptedException
+     */
+    private void waitForClick() throws InterruptedException {
+        int ct = 0;
+        while (!TestTileService.hasBeenClicked() && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertTrue(TestTileService.hasBeenClicked());
+    }
+
+    /**
+     * Waits for the runnable to be run. If it times out it fails the test.
+     * @throws InterruptedException
+     */
+    private void waitForRun(TestRunnable t) throws InterruptedException {
+        int ct = 0;
+        while (!t.hasRan && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertTrue(t.hasRan);
+    }
+
+    @Override
+    protected void waitForListening(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (TestTileService.isListening() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, TestTileService.isListening());
+    }
+
+    /**
+     * Waits for the TileService to be in the expected connected state. If it times out, it fails
+     * the test
+     * @param state desired connected state
+     * @throws InterruptedException
+     */
+    @Override
+    protected void waitForConnected(boolean state) throws InterruptedException {
+        int ct = 0;
+        while (TestTileService.isConnected() != state && (ct++ < CHECK_RETRIES)) {
+            Thread.sleep(CHECK_DELAY);
+        }
+        assertEquals(state, TestTileService.isConnected());
+    }
+
+    @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
+    protected String getComponentName() {
+        return TestTileService.getComponentName().flattenToString();
+    }
+
+    @Override
+    protected TileService getTileServiceInstance() {
+        return TestTileService.getInstance();
+    }
+
+    class TestRunnable implements Runnable {
+        boolean hasRan = false;
+
+        @Override
+        public void run() {
+            hasRan = true;
+        }
+    }
+}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java b/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java
new file mode 100644
index 0000000..0e2e660
--- /dev/null
+++ b/tests/quicksettings/src/android/quicksettings/cts/ToggleableTestTileService.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.quicksettings.cts;
+
+import android.content.ComponentName;
+import android.service.quicksettings.Tile;
+
+public class ToggleableTestTileService extends TestTileService {
+    public static final String TAG = "ToggleableTestTileService";
+    public static final String PKG = "android.app.stubs";
+    public static final int ICON_ID = R.drawable.robot;
+
+    private static TestTileService sTestTileService = null;
+
+    public static boolean isConnected() {
+        return getInstance() != null && getInstance().isConnected.get();
+    }
+
+    public static boolean isListening() {
+        return getInstance().isListening.get();
+    }
+
+    public static TestTileService getInstance() {
+        return ToggleableTestTileService.sTestTileService;
+    }
+
+    @Override
+    public void setInstance(TestTileService tile) {
+        sTestTileService = tile;
+    }
+
+    public static String getId() {
+        return String.format("%s/%s", ToggleableTestTileService.class.getPackage().getName(),
+                ToggleableTestTileService.class.getName());
+    }
+
+    public static ComponentName getComponentName() {
+        return new ComponentName(ToggleableTestTileService.class.getPackage().getName(),
+                ToggleableTestTileService.class.getName());
+    }
+
+    public void toggleState() {
+        if (isListening()) {
+            Tile tile = getInstance().getQsTile();
+            switch(tile.getState()) {
+                case Tile.STATE_ACTIVE:
+                    tile.setState(Tile.STATE_INACTIVE);
+                    break;
+                case Tile.STATE_INACTIVE:
+                    tile.setState(Tile.STATE_ACTIVE);
+                    break;
+                default:
+                    break;
+            }
+            tile.updateTile();
+        }
+    }
+}
diff --git a/tests/rollback/AndroidTest.xml b/tests/rollback/AndroidTest.xml
index 534ed1e..0194557 100644
--- a/tests/rollback/AndroidTest.xml
+++ b/tests/rollback/AndroidTest.xml
@@ -24,7 +24,11 @@
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="run-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
         <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.A" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.B" />
+        <option name="teardown-command" value="pm uninstall com.android.cts.install.lib.testapp.C" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest">
         <option name="package" value="com.android.cts.rollback"/>
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index af23ff5..05c4ca7 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -21,17 +21,24 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.fail;
+
 import android.Manifest;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.rollback.RollbackInfo;
+import android.content.rollback.RollbackManager;
 import android.provider.DeviceConfig;
 
 import androidx.test.InstrumentationRegistry;
 
 import com.android.cts.install.lib.Install;
 import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
 import com.android.cts.install.lib.TestApp;
 import com.android.cts.install.lib.Uninstall;
 import com.android.cts.rollback.lib.Rollback;
+import com.android.cts.rollback.lib.RollbackBroadcastReceiver;
 import com.android.cts.rollback.lib.RollbackUtils;
 
 import org.junit.After;
@@ -41,12 +48,19 @@
 import org.junit.runners.JUnit4;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.TimeUnit;
 
 /**
  * CTS Tests for RollbackManager APIs.
  */
 @RunWith(JUnit4.class)
 public class RollbackManagerTest {
+    // TODO: use PackageManager.RollbackDataPolicy.* when they are system API
+    private static final int ROLLBACK_DATA_POLICY_RESTORE = 0;
+    private static final int ROLLBACK_DATA_POLICY_WIPE = 1;
+
+    private static final String PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS = "enable_rollback_timeout";
 
     /**
      * Adopts common permissions needed to test rollbacks and uninstalls the
@@ -58,11 +72,14 @@
                 .adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES,
                     Manifest.permission.DELETE_PACKAGES,
+                    Manifest.permission.MANAGE_ROLLBACKS,
                     Manifest.permission.TEST_MANAGE_ROLLBACKS,
                     Manifest.permission.READ_DEVICE_CONFIG,
-                    Manifest.permission.WRITE_DEVICE_CONFIG);
+                    Manifest.permission.WRITE_DEVICE_CONFIG,
+                    Manifest.permission.FORCE_STOP_PACKAGES,
+                    Manifest.permission.SET_TIME);
 
-        Uninstall.packages(TestApp.A);
+        Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
     }
 
     /**
@@ -70,7 +87,7 @@
      */
     @After
     public void teardown() throws InterruptedException, IOException {
-        Uninstall.packages(TestApp.A);
+        Uninstall.packages(TestApp.A, TestApp.B, TestApp.C);
 
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .dropShellPermissionIdentity();
@@ -110,12 +127,227 @@
         assertThat(committed).causePackagesContainsExactly(TestApp.A2);
     }
 
+    /**
+     * Tests rollback of multi-package installs is implemented.
+     */
+    @Test
+    public void testBasic_MultiPackage() throws Exception {
+        // Prep installation of the test apps.
+        Install.multi(TestApp.A1, TestApp.B1).commit();
+        Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+        // TestApp.A should now be available for rollback.
+        RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(rollback).isNotNull();
+        assertThat(rollback).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1),
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+
+        // Rollback the app. It should cause both test apps to be rolled back.
+        RollbackUtils.rollback(rollback.getRollbackId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+        // We should see recent rollbacks listed for both A and B.
+        RollbackInfo rollbackA = RollbackUtils.getCommittedRollback(TestApp.A);
+        RollbackInfo rollbackB = RollbackUtils.getCommittedRollback(TestApp.B);
+        assertThat(rollbackA).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1),
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+        assertThat(rollbackA).hasRollbackId(rollback.getRollbackId());
+        assertThat(rollbackB).hasRollbackId(rollback.getRollbackId());
+    }
+
+    /**
+     * Tests rollbacks are properly persisted.
+     */
+    @Test
+    public void testSingleRollbackPersistence() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(rollbackA).isNotNull();
+
+        // Check the available rollback is persisted correctly
+        rm.reloadPersistedData();
+        rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
+        assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+
+        // Rollback the app
+        TestApp cause = new TestApp("Foo", "com.android.tests.rollback.testapp.Foo",
+                /*versionCode*/ 42, /*isApex*/ false);
+        RollbackUtils.rollback(rollbackA.getRollbackId(), cause);
+        RollbackInfo committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).isNotNull();
+
+        // Check the committed rollback is persisted correctly
+        rm.reloadPersistedData();
+        committed = RollbackUtils.getCommittedRollback(TestApp.A);
+        assertThat(committed).hasRollbackId(rollbackA.getRollbackId());
+        assertThat(committed).isNotStaged();
+        assertThat(committed).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        assertThat(committed).causePackagesContainsExactly(cause);
+    }
+
+    /**
+     * Tests rollbacks are properly persisted.
+     */
+    @Test
+    public void testMultiRollbackPersistence() throws Exception {
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+
+        Install.multi(TestApp.A1, TestApp.B1).commit();
+        Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+        RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
+        assertThat(rollbackA).isNotNull();
+        assertThat(rollbackB).isNotNull();
+
+        // Check the available rollback is persisted correctly
+        rm.reloadPersistedData();
+        rollbackA = RollbackUtils.getAvailableRollback(TestApp.A);
+        rollbackB = RollbackUtils.getAvailableRollback(TestApp.B);
+        assertThat(rollbackB).hasRollbackId(rollbackA.getRollbackId());
+        assertThat(rollbackA).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1),
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+
+        // Rollback the app
+        RollbackUtils.rollback(rollbackA.getRollbackId());
+        RollbackInfo committedA = RollbackUtils.getCommittedRollback(TestApp.A);
+        RollbackInfo committedB = RollbackUtils.getCommittedRollback(TestApp.B);
+        assertThat(committedA).isNotNull();
+        assertThat(committedB).isNotNull();
+
+        // Check the committed rollback is persisted correctly
+        rm.reloadPersistedData();
+        committedA = RollbackUtils.getCommittedRollback(TestApp.A);
+        committedB = RollbackUtils.getCommittedRollback(TestApp.B);
+        assertThat(committedA).hasRollbackId(rollbackA.getRollbackId());
+        assertThat(committedB).hasRollbackId(rollbackA.getRollbackId());
+        assertThat(committedA).isNotStaged();
+        assertThat(committedA).packagesContainsExactly(
+                Rollback.from(TestApp.A2).to(TestApp.A1),
+                Rollback.from(TestApp.B2).to(TestApp.B1));
+    }
+
+    /**
+     * Tests that the MANAGE_ROLLBACKS permission is required to call
+     * RollbackManager APIs.
+     */
+    @Test
+    public void testManageRollbacksPermission() throws Exception {
+        // We shouldn't be allowed to call any of the RollbackManager APIs
+        // without the MANAGE_ROLLBACKS permission.
+        InstallUtils.dropShellPermissionIdentity();
+        RollbackManager rm = RollbackUtils.getRollbackManager();
+
+        try {
+            rm.getAvailableRollbacks();
+            fail("expected SecurityException");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+
+        try {
+            rm.getRecentlyCommittedRollbacks();
+            fail("expected SecurityException");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+
+        try {
+            LocalIntentSender sender = new LocalIntentSender();
+            rm.commitRollback(0, Collections.emptyList(), sender.getIntentSender());
+            fail("expected SecurityException");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+
+        try {
+            rm.reloadPersistedData();
+            fail("expected SecurityException");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+
+        try {
+            rm.expireRollbackForPackage(TestApp.A);
+            fail("expected SecurityException");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    /**
+     * Tests that you cannot enable rollback for a package without the
+     * MANAGE_ROLLBACKS permission.
+     */
+    @Test
+    public void testEnableRollbackPermission() throws Exception {
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES);
+
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+
+        // We expect v2 of the app was installed, but rollback has not
+        // been enabled.
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.MANAGE_ROLLBACKS,
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES);
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+    }
+
+    /**
+     * Tests that you cannot enable rollback for a non-module package when
+     * holding the MANAGE_ROLLBACKS permission without TEST_MANAGE_ROLLBACKS.
+     */
+    @Test
+    public void testNonModuleEnableRollback() throws Exception {
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES,
+                Manifest.permission.MANAGE_ROLLBACKS);
+
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+
+        // We expect v2 of the app was installed, but rollback has not
+        // been enabled because the test app is not a module.
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+    }
+
+    /**
+     * Tests failure to enable rollback for multi-package installs.
+     * If any one of the packages fail to enable rollback, we shouldn't enable
+     * rollback for any package.
+     */
+    @Test
+    public void testMultiPackageEnableFail() throws Exception {
+        Install.single(TestApp.A1).commit();
+        // We should fail to enable rollback here because TestApp B is not
+        // already installed.
+        Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
+    }
+
     @Test
     public void testGetRollbackDataPolicy() throws Exception {
-        // TODO: To change to the following statement when
-        // PackageManager.RollbackDataPolicy.WIPE is available.
-        // final int rollBackDataPolicy = PackageManager.RollbackDataPolicy.WIPE;
-        final int rollBackDataPolicy = 1;
+        final int rollBackDataPolicy = ROLLBACK_DATA_POLICY_WIPE;
 
         Install.single(TestApp.A1).commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -181,4 +413,345 @@
         RollbackUtils.rollback(available.getRollbackId(), TestApp.ARotated2);
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
     }
+
+    /**
+     * Test we can't enable rollback for non-allowlisted app without
+     * TEST_MANAGE_ROLLBACKS permission
+     */
+    @Test
+    public void testNonRollbackAllowlistedApp() throws Exception {
+        InstallUtils.dropShellPermissionIdentity();
+        InstallUtils.adoptShellPermissionIdentity(
+                Manifest.permission.INSTALL_PACKAGES,
+                Manifest.permission.DELETE_PACKAGES,
+                Manifest.permission.MANAGE_ROLLBACKS);
+
+        Install.single(TestApp.A1).commit();
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+    }
+
+    /**
+     * Tests user data is restored according to the preset rollback data policy.
+     */
+    @Test
+    public void testRollbackDataPolicy() throws Exception {
+        Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
+        // Write user data version = 1
+        InstallUtils.processUserData(TestApp.A);
+        InstallUtils.processUserData(TestApp.B);
+        InstallUtils.processUserData(TestApp.C);
+
+        Install a2 = Install.single(TestApp.A2)
+                .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_WIPE);
+        Install b2 = Install.single(TestApp.B2)
+                .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RESTORE);
+        Install c2 = Install.single(TestApp.C2)
+                .setEnableRollback(PackageManager.ROLLBACK_DATA_POLICY_RETAIN);
+        Install.multi(a2, b2, c2).setEnableRollback().commit();
+        // Write user data version = 2
+        InstallUtils.processUserData(TestApp.A);
+        InstallUtils.processUserData(TestApp.B);
+        InstallUtils.processUserData(TestApp.C);
+
+        RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
+        RollbackUtils.rollback(info.getRollbackId());
+        // Read user data version from userdata.txt
+        // A's user data version is -1 for user data is wiped.
+        // B's user data version is 1 for user data is restored.
+        // C's user data version is 2 for user data is retained.
+        assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
+        assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
+    }
+
+    /**
+     * Tests user data is restored according to the rollback data policy defined in the manifest.
+     */
+    @Test
+    public void testRollbackDataPolicy_Manifest() throws Exception {
+        Install.multi(TestApp.A1, TestApp.B1, TestApp.C1).commit();
+        // Write user data version = 1
+        InstallUtils.processUserData(TestApp.A);
+        InstallUtils.processUserData(TestApp.B);
+        InstallUtils.processUserData(TestApp.C);
+
+        Install a2 = Install.single(TestApp.ARollbackWipe2).setEnableRollback();
+        Install b2 = Install.single(TestApp.BRollbackRestore2).setEnableRollback();
+        Install c2 = Install.single(TestApp.CRollbackRetain2).setEnableRollback();
+        Install.multi(a2, b2, c2).setEnableRollback().commit();
+        // Write user data version = 2
+        InstallUtils.processUserData(TestApp.A);
+        InstallUtils.processUserData(TestApp.B);
+        InstallUtils.processUserData(TestApp.C);
+
+        RollbackInfo info = RollbackUtils.getAvailableRollback(TestApp.A);
+        RollbackUtils.rollback(info.getRollbackId());
+        // Read user data version from userdata.txt
+        // A's user data version is -1 for user data is wiped.
+        // B's user data version is 1 for user data is restored.
+        // C's user data version is 2 for user data is retained.
+        assertThat(InstallUtils.getUserDataVersion(TestApp.A)).isEqualTo(-1);
+        assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
+        assertThat(InstallUtils.getUserDataVersion(TestApp.C)).isEqualTo(2);
+    }
+
+    /**
+     * Test explicit expiration of rollbacks.
+     * Does not test the scheduling aspects of rollback expiration.
+     */
+    @Test
+    public void testRollbackExpiration() throws Exception {
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+        // Expire the rollback.
+        RollbackUtils.getRollbackManager().expireRollbackForPackage(TestApp.A);
+
+        // The rollback should no longer be available.
+        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+    }
+
+    /**
+     * Test restrictions on rollback broadcast sender.
+     * A random app should not be able to send a ROLLBACK_COMMITTED broadcast.
+     */
+    @Test
+    public void testRollbackBroadcastRestrictions() throws Exception {
+        RollbackBroadcastReceiver broadcastReceiver = new RollbackBroadcastReceiver();
+        Intent broadcast = new Intent(Intent.ACTION_ROLLBACK_COMMITTED);
+        try {
+            InstrumentationRegistry.getContext().sendBroadcast(broadcast);
+            fail("Succeeded in sending restricted broadcast from app context.");
+        } catch (SecurityException se) {
+            // Expected behavior.
+        }
+
+        // Confirm that we really haven't received the broadcast.
+        // TODO: How long to wait for the expected timeout?
+        assertThat(broadcastReceiver.poll(5, TimeUnit.SECONDS)).isNull();
+
+        // TODO: Do we need to do this? Do we need to ensure this is always
+        // called, even when the test fails?
+        broadcastReceiver.unregister();
+    }
+
+    /**
+     * Test rollback of apks involving splits.
+     */
+    @Test
+    public void testRollbackWithSplits() throws Exception {
+        Install.single(TestApp.ASplit1).commit();
+        Install.single(TestApp.ASplit2).setEnableRollback().commit();
+        RollbackInfo rollback = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        RollbackUtils.rollback(rollback.getRollbackId());
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+    }
+
+    /**
+     * Test bad update automatic rollback.
+     */
+    @Test
+    public void testBadUpdateRollback() throws Exception {
+        // Prep installation of the test apps.
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.ACrashing2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+        Install.single(TestApp.B1).commit();
+        Install.single(TestApp.B2).setEnableRollback().commit();
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+        // Both test apps should now be available for rollback, and the
+        // targetPackage returned for rollback should be correct.
+        RollbackInfo rollbackA = RollbackUtils.waitForAvailableRollback(TestApp.A);
+        assertThat(rollbackA).packagesContainsExactly(Rollback.from(TestApp.A2).to(TestApp.A1));
+        RollbackInfo rollbackB = RollbackUtils.waitForAvailableRollback(TestApp.B);
+        assertThat(rollbackB).packagesContainsExactly(Rollback.from(TestApp.B2).to(TestApp.B1));
+
+        // Register rollback committed receiver
+        RollbackBroadcastReceiver rollbackReceiver = new RollbackBroadcastReceiver();
+
+        // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+        RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+
+        // Verify we received a broadcast for the rollback.
+        assertThat(rollbackReceiver.poll(1, TimeUnit.MINUTES)).isNotNull();
+
+        // TestApp.A is automatically rolled back by the RollbackPackageHealthObserver
+        assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+        // Instrumented app is still the package installer
+        String installer = InstrumentationRegistry.getContext().getPackageManager()
+                .getInstallerPackageName(TestApp.A);
+        assertThat(installer).isEqualTo(getClass().getPackageName());
+        // TestApp.B is untouched
+        assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+    }
+
+    /**
+     * Tests we fail to enable rollbacks if enable-rollback times out.
+     */
+    @Test
+    public void testEnableRollbackTimeoutFailsRollback() throws Exception {
+        //setting the timeout to a very short amount that will definitely be triggered
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+                Long.toString(0), false /* makeDefault*/);
+
+        try {
+            Install.single(TestApp.A1).commit();
+            RollbackUtils.waitForUnavailableRollback(TestApp.A);
+
+            // Block the RollbackManager to make extra sure it will not be
+            // able to enable the rollback in time.
+            RollbackManager rm = RollbackUtils.getRollbackManager();
+            rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(1));
+            Install.single(TestApp.A2).setEnableRollback().commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+            // Give plenty of time for RollbackManager to unblock and attempt
+            // to make the rollback available before asserting that the
+            // rollback was not made available.
+            Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+        } finally {
+            //setting the timeout back to default
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                    PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+                    null, false /* makeDefault*/);
+        }
+    }
+
+    /**
+     * Tests we fail to enable rollbacks if enable-rollback times out for any child session.
+     */
+    @Test
+    public void testEnableRollbackTimeoutFailsRollback_MultiPackage() throws Exception {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+                Long.toString(5000), false /* makeDefault*/);
+
+        try {
+            Install.multi(TestApp.A1, TestApp.B1).commit();
+            RollbackUtils.waitForUnavailableRollback(TestApp.A);
+
+            // Block the 2nd session for 10s so it will not be able to enable the rollback in time.
+            RollbackManager rm = RollbackUtils.getRollbackManager();
+            rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(0));
+            rm.blockRollbackManager(TimeUnit.SECONDS.toMillis(10));
+            Install.multi(TestApp.A2, TestApp.B2).setEnableRollback().commit();
+            assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+            assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+
+            // Give plenty of time for RollbackManager to unblock and attempt
+            // to make the rollback available before asserting that the
+            // rollback was not made available.
+            Thread.sleep(TimeUnit.SECONDS.toMillis(2));
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.B)).isNull();
+        } finally {
+            //setting the timeout back to default
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+                    PROPERTY_ENABLE_ROLLBACK_TIMEOUT_MILLIS,
+                    null, false /* makeDefault*/);
+        }
+    }
+
+    /**
+     * Test the scheduling aspect of rollback expiration.
+     */
+    @Test
+    public void testRollbackExpiresAfterLifetime() throws Exception {
+        long expirationTime = TimeUnit.SECONDS.toMillis(30);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                Long.toString(expirationTime), false /* makeDefault*/);
+
+        try {
+            Install.single(TestApp.A1).commit();
+            Install.single(TestApp.A2).setEnableRollback().commit();
+            RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+            // Give it a little more time, but still not long enough to expire
+            Thread.sleep(expirationTime / 2);
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+
+            // Check that the data has expired after the expiration time (with a buffer of 1 second)
+            Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
+            assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+        } finally {
+            // Restore default config values
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                    RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                    null, false /* makeDefault*/);
+        }
+    }
+
+    /**
+     * Test that available rollbacks should expire correctly when the property
+     * {@link RollbackManager#PROPERTY_ROLLBACK_LIFETIME_MILLIS} is changed
+     */
+    @Test
+    public void testRollbackExpiresWhenLifetimeChanges() throws Exception {
+        Install.single(TestApp.A1).commit();
+        Install.single(TestApp.A2).setEnableRollback().commit();
+        RollbackUtils.waitForAvailableRollback(TestApp.A);
+
+        // Change the lifetime to 0 which should expire rollbacks immediately
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                Long.toString(0), false /* makeDefault*/);
+
+        try {
+            RollbackUtils.waitForUnavailableRollback(TestApp.A);
+        } finally {
+            // Restore default config values
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                    RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                    null, false /* makeDefault*/);
+        }
+    }
+
+    /**
+     * Test that changing time on device does not affect the duration of time that we keep
+     * rollback available
+     */
+    @Test
+    public void testTimeChangeDoesNotAffectLifetime() throws Exception {
+        long expirationTime = TimeUnit.SECONDS.toMillis(30);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                Long.toString(expirationTime), false /* makeDefault*/);
+
+        try {
+            Install.single(TestApp.A1).commit();
+            Install.single(TestApp.A2).setEnableRollback().commit();
+            RollbackUtils.waitForAvailableRollback(TestApp.A);
+            RollbackUtils.forwardTimeBy(expirationTime);
+
+            try {
+                // The rollback should be still available after forwarding time
+                assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+                // The rollback shouldn't expire when half of the expiration time elapses
+                Thread.sleep(expirationTime / 2);
+                assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNotNull();
+                // The rollback now should expire
+                Thread.sleep(expirationTime / 2 + TimeUnit.SECONDS.toMillis(1));
+                assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
+            } finally {
+                RollbackUtils.forwardTimeBy(-expirationTime);
+            }
+        } finally {
+            // Restore default config values
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+                    RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
+                    null, false /* makeDefault*/);
+        }
+    }
 }
diff --git a/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java b/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
index 7576ae8..8ab556c 100644
--- a/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
+++ b/tests/rotationresolverservice/src/android/rotationresolverservice/cts/CtsRotationResolverServiceDeviceTest.java
@@ -62,6 +62,7 @@
     private static final boolean FAKE_SHOULD_USE_CAMERA = true;
     private static final long FAKE_TIME_OUT = 1000L;
     private static final long TEMPORARY_SERVICE_DURATION = 5000L;
+    private static final String USER_ID = "0";
 
     private final boolean isTestable =
             !TextUtils.isEmpty(getRotationResolverServiceComponent());
@@ -150,7 +151,7 @@
     }
 
     private String getRotationResolverServiceComponent() {
-        return runShellCommand("cmd resolver get-bound-package");
+        return runShellCommand("cmd resolver get-bound-package %s", USER_ID);
     }
 
     private int getLastTestCallbackCode() {
@@ -164,16 +165,17 @@
      * in our test service w/ CountDownLatch(s).
      */
     private void callResolveRotation() {
-        runShellCommand("cmd resolver resolve-rotation");
+        runShellCommand("cmd resolver resolve-rotation %s", USER_ID);
         CtsTestRotationResolverService.onReceivedResponse();
     }
 
     private void setTestableRotationResolverService(String service) {
-        runShellCommand("cmd resolver set-temporary-service %s %s", service, TEMPORARY_SERVICE_DURATION);
+        runShellCommand("cmd resolver set-temporary-service %s %s %s",
+                USER_ID, service, TEMPORARY_SERVICE_DURATION);
     }
 
     private void clearTestableRotationResolverService() {
-        runShellCommand("cmd resolver set-temporary-service");
+        runShellCommand("cmd resolver set-temporary-service %s", USER_ID);
     }
 
 }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 74f28ba..8bc910d 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -85,7 +85,8 @@
                 acquireWakeLock();
             }
         };
-        mContext.registerReceiver(receiver, intentFilter);
+        mContext.registerReceiver(receiver, intentFilter,
+                Context.RECEIVER_NOT_EXPORTED);
 
         AlarmManager am = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
         long wakeupTimeMs = (System.currentTimeMillis()
diff --git a/tests/signature/intent-check/DynamicConfig.xml b/tests/signature/intent-check/DynamicConfig.xml
index 59fe44d..238d445 100644
--- a/tests/signature/intent-check/DynamicConfig.xml
+++ b/tests/signature/intent-check/DynamicConfig.xml
@@ -26,9 +26,6 @@
     Bug: 150153196 android.intent.action.LOAD_DATA (system in API 30)
     Bug: 150153196 android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY (system in API 30)
     Bug: 186495404 android.intent.action.REBOOT_READY
-    Bug: 206897736 android.intent.action.MANAGE_PERMISSION_USAGE
-    Bug: 206897736 android.intent.action.VIEW_APP_FEATURES
-    Bug: 209528070 android.intent.action.APPLICATION_LOCALE_CHANGED
 -->
 <dynamicConfig>
     <entry key ="intent_whitelist">
@@ -43,8 +40,5 @@
       <value>android.intent.action.LOAD_DATA</value>
       <value>android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY</value>
       <value>android.intent.action.REBOOT_READY</value>
-      <value>android.intent.action.MANAGE_PERMISSION_USAGE</value>
-      <value>android.intent.action.VIEW_APP_FEATURES</value>
-      <value>android.intent.action.APPLICATION_LOCALE_CHANGED</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java b/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
index fa839af..328ed39 100644
--- a/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
+++ b/tests/signature/lib/android/src/android/signature/cts/DexMemberChecker.java
@@ -17,7 +17,7 @@
 package android.signature.cts;
 
 import android.util.Log;
-import java.lang.reflect.Constructor;
+
 import java.lang.reflect.Executable;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -97,11 +97,8 @@
             if (jni) {
                 try {
                     observer.fieldAccessibleViaJni(hasMatchingField_JNI(klass, field), field);
-                } catch (ClassNotFoundException | ExceptionInInitializerError | UnsatisfiedLinkError
-                        | NoClassDefFoundError e) {
-                    if ((e instanceof NoClassDefFoundError)
-                            && !(e.getCause() instanceof ExceptionInInitializerError)
-                            && !(e.getCause() instanceof UnsatisfiedLinkError)) {
+                } catch (ClassNotFoundException | Error e) {
+                    if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
                         throw (NoClassDefFoundError) e;
                     }
 
@@ -122,11 +119,8 @@
             if (jni) {
                 try {
                     observer.methodAccessibleViaJni(hasMatchingMethod_JNI(klass, method), method);
-                } catch (ExceptionInInitializerError | UnsatisfiedLinkError
-                        | NoClassDefFoundError e) {
-                    if ((e instanceof NoClassDefFoundError)
-                            && !(e.getCause() instanceof ExceptionInInitializerError)
-                            && !(e.getCause() instanceof UnsatisfiedLinkError)) {
+                } catch (Error e) {
+                    if ((e instanceof NoClassDefFoundError) && !(e.getCause() instanceof Error)) {
                         throw e;
                     }
 
diff --git a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
index fe67157..e5e6c84 100644
--- a/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
+++ b/tests/signature/lib/common/src/android/signature/cts/ApiComplianceChecker.java
@@ -22,7 +22,9 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Formatter;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -48,6 +50,46 @@
                         + "int,android.telephony.euicc.DownloadableSubscription,boolean,boolean)");
     }
 
+    /**
+     * A set of field values signatures whose value modifier should be ignored.
+     *
+     * <p>If a field value is intended to be changed to correct its value, that change should be
+     * allowed. The field name is the key of the ignoring map, and a FieldValuePair which is a pair
+     * of the old value and the new value is the value of the ignoring map.
+     * WARNING: Entries should only be added after consulting API council.
+     */
+    private static class FieldValuePair {
+        private String oldValue;
+        private String newValue;
+
+        private FieldValuePair(String oldValue, String newValue) {
+            this.oldValue = oldValue;
+            this.newValue = newValue;
+        }
+    };
+    private static final Map<String, FieldValuePair> IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST =
+            new HashMap<String, FieldValuePair>();
+    static {
+        // This field value was previously wrong. As the CtsSystemApiSignatureTestCases package
+        // tests both the old and new specifications with both old and new values, this needs to be
+        // ignored.
+        IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+                "android.media.tv.tuner.frontend.FrontendSettings#FEC_28_45(long)",
+                new FieldValuePair("-2147483648", "2147483648"));
+        IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+                "android.media.tv.tuner.frontend.FrontendSettings#FEC_29_45(long)",
+                new FieldValuePair("1", "4294967296"));
+        IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+                "android.media.tv.tuner.frontend.FrontendSettings#FEC_31_45(long)",
+                new FieldValuePair("2", "8589934592"));
+        IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+                "android.media.tv.tuner.frontend.FrontendSettings#FEC_32_45(long)",
+                new FieldValuePair("4", "17179869184"));
+        IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.put(
+                "android.media.tv.tuner.frontend.FrontendSettings#FEC_77_90(long)",
+                new FieldValuePair("8", "34359738368"));
+    }
+
     /** Indicates that the class is an annotation. */
     private static final int CLASS_MODIFIER_ANNOTATION = 0x00002000;
 
@@ -311,7 +353,7 @@
                             expectedFieldType, actualFieldType));
         }
 
-        String message = checkFieldValueCompliance(fieldDescription, field);
+        String message = checkFieldValueCompliance(classDescription, fieldDescription, field);
         if (message != null) {
             resultObserver.notifyFailure(FailureType.MISMATCH_FIELD,
                     fieldDescription.toReadableString(classDescription.getAbsoluteClassName()),
@@ -363,7 +405,8 @@
      * @param apiField The field as defined by the platform API.
      * @param deviceField The field as defined by the device under test.
      */
-    private static String checkFieldValueCompliance(JDiffField apiField, Field deviceField) {
+    private static String checkFieldValueCompliance(
+            JDiffClassDescription classDescription, JDiffField apiField, Field deviceField) {
         if ((apiField.mModifier & Modifier.FINAL) == 0 ||
                 (apiField.mModifier & Modifier.STATIC) == 0) {
             // Only final static fields can have fixed values.
@@ -383,6 +426,14 @@
 
         String deviceFieldValue = getFieldValueAsString(deviceField);
         if (!Objects.equals(apiFieldValue, deviceFieldValue)) {
+            String fieldName = apiField.toReadableString(classDescription.getAbsoluteClassName());
+            if (IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.containsKey(fieldName)
+                    && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).oldValue.equals(
+                            apiFieldValue)
+                    && IGNORE_FIELD_VALUES_MODIFIER_ALLOWED_LIST.get(fieldName).newValue.equals(
+                            deviceFieldValue)) {
+                return null;
+            }
             return String.format("Incorrect field value, expected <%s>, found <%s>",
                     apiFieldValue, deviceFieldValue);
 
diff --git a/tests/tests/accounts/OWNERS b/tests/tests/accounts/OWNERS
index 695530b..0ca6900 100644
--- a/tests/tests/accounts/OWNERS
+++ b/tests/tests/accounts/OWNERS
@@ -1,5 +1,4 @@
 # Bug component: 82728
-carlosvaldivia@google.com
-dementyev@google.com
-sandrakwan@google.com
 aseemk@google.com
+dementyev@google.com
+mpape@google.com
diff --git a/tests/tests/activityrecognition/AndroidTest.xml b/tests/tests/activityrecognition/AndroidTest.xml
index 86536b7..b4bce63 100644
--- a/tests/tests/activityrecognition/AndroidTest.xml
+++ b/tests/tests/activityrecognition/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="test-suite-tag" value="cts" />
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index 29ba591..79cb676 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -26,6 +26,7 @@
         "ctstestrunner-axt",
         "cts-wm-util",
         "junit",
+        "permission-test-util-lib",
         "ub-uiautomator",
     ],
     libs: [
diff --git a/tests/tests/app.usage/AndroidManifest.xml b/tests/tests/app.usage/AndroidManifest.xml
index 141a9af..75272ee 100644
--- a/tests/tests/app.usage/AndroidManifest.xml
+++ b/tests/tests/app.usage/AndroidManifest.xml
@@ -28,6 +28,7 @@
     <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
 
     <application android:usesCleartextTraffic="true"
             android:networkSecurityConfig="@xml/network_security_config">
diff --git a/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
index d16a12b..e7a374c 100644
--- a/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
+++ b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
@@ -17,4 +17,5 @@
 
 interface ITestReceiver {
     boolean isAppInactive(String pkg);
+    void generateAndSendNotification();
 }
\ No newline at end of file
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
index eb34e95..bfe475f 100644
--- a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
@@ -16,14 +16,23 @@
 
 package android.app.usage.cts.test1;
 
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.app.Service;
 import android.app.usage.UsageStatsManager;
 import android.app.usage.cts.ITestReceiver;
-import android.content.Context;
 import android.content.Intent;
 import android.os.IBinder;
+import android.provider.Settings;
 
 public class TestService extends Service {
+    private static final String TEST_CHANNEL_ID = "test_channel_id";
+    private static final String TEST_CHANNEL_NAME = "test_channel_name";
+    private static final String TEST_CHANNEL_DESC = "Test channel";
+    private static final int TEST_NOTIFICATION_ID = 11;
+
     @Override
     public IBinder onBind(Intent intent) {
         return new TestReceiver();
@@ -32,9 +41,28 @@
     private class TestReceiver extends ITestReceiver.Stub {
         @Override
         public boolean isAppInactive(String pkg) {
-            UsageStatsManager usm = (UsageStatsManager) getSystemService(
-                    Context.USAGE_STATS_SERVICE);
+            UsageStatsManager usm = getSystemService(UsageStatsManager.class);
             return usm.isAppInactive(pkg);
         }
+
+        @Override
+        public void generateAndSendNotification() {
+            final NotificationManager notificationManager =
+                    getSystemService(NotificationManager.class);
+            final NotificationChannel mChannel = new NotificationChannel(TEST_CHANNEL_ID,
+                    TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT);
+            // Configure the notification channel.
+            mChannel.setDescription(TEST_CHANNEL_DESC);
+            notificationManager.createNotificationChannel(mChannel);
+            final Notification.Builder mBuilder =
+                    new Notification.Builder(getApplicationContext(), TEST_CHANNEL_ID)
+                            .setSmallIcon(android.R.drawable.ic_info)
+                            .setContentTitle("My notification")
+                            .setContentText("Hello World!");
+            final PendingIntent pi = PendingIntent.getActivity(getApplicationContext(), 1,
+                    new Intent(Settings.ACTION_SETTINGS), PendingIntent.FLAG_IMMUTABLE);
+            mBuilder.setContentIntent(pi);
+            notificationManager.notify(TEST_NOTIFICATION_ID, mBuilder.build());
+        }
     }
 }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java b/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
index decc9d8..0efa65f 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/TestJob.java
@@ -23,19 +23,14 @@
 import android.content.ComponentName;
 import android.content.Context;
 
-import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
 
 public final class TestJob extends JobService {
 
     public static final int TEST_JOB_ID = 1;
     public static final String NOTIFICATION_CHANNEL_ID = TestJob.class.getSimpleName();
     private static boolean sJobStarted;
-    public static BooleanSupplier hasJobStarted = new BooleanSupplier() {
-        @Override
-        public boolean getAsBoolean() {
-            return sJobStarted;
-        }
-    };
+    public static Supplier<Boolean> hasJobStarted = () -> sJobStarted;
 
     @Override
     public boolean onStartJob(JobParameters params) {
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index a20a8f9..ee0d10e 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -16,6 +16,12 @@
 
 package android.app.usage.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
+import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
+import static android.provider.DeviceConfig.NAMESPACE_APP_STANDBY;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -49,9 +55,12 @@
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.Parcel;
+import android.os.Process;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.AppModeInstant;
 import android.provider.Settings;
@@ -71,6 +80,8 @@
 
 import com.android.compatibility.common.util.AppStandbyUtils;
 import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.DeviceConfigStateHelper;
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
 
 import org.junit.After;
@@ -86,11 +97,12 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
-import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
 
 /**
  * Test the UsageStats API. It is difficult to test the entire surface area
@@ -140,6 +152,14 @@
     private static final ComponentName TEST_APP2_PIP_COMPONENT = new ComponentName(TEST_APP2_PKG,
             TEST_APP2_CLASS_PIP);
 
+    // TODO(206518483): Define these constants in UsageStatsManager to avoid hardcoding here.
+    private static final String KEY_NOTIFICATION_SEEN_HOLD_DURATION =
+            "notification_seen_duration";
+    private static final String KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET =
+            "notification_seen_promoted_bucket";
+
+    private static final int DEFAULT_TIMEOUT_MS = 10_000;
+
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
     private static final long DAY = TimeUnit.DAYS.toMillis(1);
@@ -173,6 +193,7 @@
                 Context.USAGE_STATS_SERVICE);
         mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
         mTargetPackage = mContext.getPackageName();
+        PermissionUtils.grantPermission(mTargetPackage, POST_NOTIFICATIONS);
 
         mWMStateHelper = new WindowManagerStateHelper();
 
@@ -200,6 +221,14 @@
             removeUser(mOtherUser);
             mOtherUser = 0;
         }
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mTargetPackage,
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL);
     }
 
     private static void assertLessThan(long left, long right) {
@@ -259,6 +288,19 @@
 
     @AppModeFull(reason = "No usage events access in instant apps")
     @Test
+    public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception {
+        mUiDevice.wakeUp();
+        dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+        final long startTime = System.currentTimeMillis();
+        launchSubActivity(Activities.ActivityOne.class);
+        final long endTime = System.currentTimeMillis();
+
+        verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage);
+    }
+
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
     public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception {
         mUiDevice.wakeUp();
         dismissKeyguard(); // also want to start out with the keyguard dismissed.
@@ -311,6 +353,17 @@
         verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG);
     }
 
+    private void verifyLastTimeVisibleWithinRange(
+            long startTime, long endTime, String targetPackage) {
+        final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
+                startTime, endTime);
+        final UsageStats stats = map.get(targetPackage);
+        assertNotNull(stats);
+        final long lastTimeVisible = stats.getLastTimeVisible();
+        assertLessThanOrEqual(startTime, lastTimeVisible);
+        assertLessThanOrEqual(lastTimeVisible, endTime);
+    }
+
     private void verifyLastTimeAnyComponentUsedWithinRange(
             long startTime, long endTime, String targetPackage) {
         final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
@@ -475,7 +528,10 @@
                 startTime, endTime);
         stats = events.get(mTargetPackage);
         assertEquals(startingCount + 1, stats.getAppLaunchCount());
-        mUiDevice.pressHome();
+
+        // Launch a new activity so the other sub activities go into a paused state.
+        launchTestActivity(TEST_APP_PKG, TEST_APP_CLASS);
+
         launchSubActivity(Activities.ActivityOne.class);
         launchSubActivity(Activities.ActivityTwo.class);
         launchSubActivity(Activities.ActivityThree.class);
@@ -501,7 +557,7 @@
             UsageEvents.Event event = new UsageEvents.Event();
             assertTrue(events.getNextEvent(event));
             if (event.getEventType() == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
-                found |= event.getAppStandbyBucket() == UsageStatsManager.STANDBY_BUCKET_RARE;
+                found |= event.getAppStandbyBucket() == STANDBY_BUCKET_RARE;
             }
         }
 
@@ -521,7 +577,7 @@
             assertTrue("No bucket data returned", bucketMap.size() > 0);
             final int bucket = bucketMap.getOrDefault(mTargetPackage, -1);
             assertEquals("Incorrect bucket returned for " + mTargetPackage, bucket,
-                    UsageStatsManager.STANDBY_BUCKET_RARE);
+                    STANDBY_BUCKET_RARE);
         } finally {
             AppStandbyUtils.setAppStandbyEnabledAtRuntime(origValue);
         }
@@ -554,7 +610,7 @@
             numEvents++;
             assertEquals("Event for a different package", mTargetPackage, event.getPackageName());
             if (event.getEventType() == Event.STANDBY_BUCKET_CHANGED) {
-                if (event.getAppStandbyBucket() == UsageStatsManager.STANDBY_BUCKET_RARE) {
+                if (event.getAppStandbyBucket() == STANDBY_BUCKET_RARE) {
                     rareTimeStamp = event.getTimeStamp();
                 }
                 else if (event.getAppStandbyBucket() == UsageStatsManager
@@ -736,7 +792,7 @@
     public void testNotificationSeen() throws Exception {
         final long startTime = System.currentTimeMillis();
 
-        // Skip the test for wearable devices, televisions and automotives; neither has
+        // Skip the test for wearable devices, televisions and automotives; none of them have
         // a notification shade, as notifications are shown via a different path than phones
         assumeFalse("Test cannot run on a watch- notification shade is not shown",
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
@@ -781,10 +837,56 @@
 
     @AppModeFull(reason = "No usage events access in instant apps")
     @Test
+    public void testNotificationSeen_verifyBucket() throws Exception {
+        // Skip the test for wearable devices, televisions and automotives; none of them have
+        // a notification shade, as notifications are shown via a different path than phones
+        assumeFalse("Test cannot run on a watch- notification shade is not shown",
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
+        assumeFalse("Test cannot run on a television- notifications are not shown",
+                mContext.getPackageManager().hasSystemFeature(
+                        PackageManager.FEATURE_LEANBACK_ONLY));
+        assumeFalse("Test cannot run on an automotive - notification shade is not shown",
+                mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE));
+
+        final long promotedBucketHoldDurationMs = TimeUnit.MINUTES.toMillis(2);
+        try (DeviceConfigStateHelper deviceConfigStateHelper =
+                     new DeviceConfigStateHelper(NAMESPACE_APP_STANDBY)) {
+            deviceConfigStateHelper.set(KEY_NOTIFICATION_SEEN_PROMOTED_BUCKET,
+                    String.valueOf(STANDBY_BUCKET_FREQUENT));
+            deviceConfigStateHelper.set(KEY_NOTIFICATION_SEEN_HOLD_DURATION,
+                    String.valueOf(promotedBucketHoldDurationMs));
+
+            mUiDevice.wakeUp();
+            dismissKeyguard();
+            final TestServiceConnection connection = bindToTestServiceAndGetConnection();
+            try {
+                ITestReceiver testReceiver = connection.getITestReceiver();
+                testReceiver.generateAndSendNotification();
+            } finally {
+                connection.unbind();
+            }
+            setStandByBucket(TEST_APP_PKG, "rare");
+            waitUntil(() -> mUsageStatsManager.getAppStandbyBucket(TEST_APP_PKG),
+                    STANDBY_BUCKET_RARE);
+            mUiDevice.openNotification();
+            waitUntil(() -> mUsageStatsManager.getAppStandbyBucket(TEST_APP_PKG),
+                    STANDBY_BUCKET_FREQUENT);
+            // TODO(206518483): Verify the behavior after the promoted duration expires (which
+            // currently doesn't work as expected).
+            // SystemClock.sleep(promotedBucketHoldDurationMs);
+            // assertEquals(STANDBY_BUCKET_RARE, mUsageStatsManager.getAppStandbyBucket(
+            //                 TEST_APP_PKG));
+            mUiDevice.pressHome();
+        }
+    }
+
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
     public void testNotificationInterruptionEventsObfuscation() throws Exception {
         final long startTime = System.currentTimeMillis();
 
-        // Skip the test for wearable devices and televisions; neither has a notification shade.
+        // Skip the test for wearable devices and televisions; none of them have a
+        // notification shade.
         assumeFalse("Test cannot run on a watch- notification shade is not shown",
                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH));
         assumeFalse("Test cannot run on a television- notifications are not shown",
@@ -1099,16 +1201,10 @@
         return events;
     }
 
-    private void waitUntil(BooleanSupplier condition, boolean expected) throws Exception {
-        final long sleepTimeMs = 500;
-        final int count = 10;
-        for (int i = 0; i < count; ++i) {
-            if (condition.getAsBoolean() == expected) {
-                return;
-            }
-            Thread.sleep(sleepTimeMs);
-        }
-        fail("Condition wasn't satisfied after " + (sleepTimeMs * count) + "ms");
+    private <T> void waitUntil(Supplier<T> resultSupplier, T expectedResult) throws Exception {
+        final T actualResult = PollingCheck.waitFor(DEFAULT_TIMEOUT_MS, resultSupplier,
+                result -> Objects.equals(expectedResult, result));
+        assertEquals(expectedResult, actualResult);
     }
 
     static class AggrEventData {
@@ -1832,11 +1928,16 @@
     }
 
     private ITestReceiver bindToTestService() throws Exception {
+        final TestServiceConnection connection = bindToTestServiceAndGetConnection();
+        return connection.getITestReceiver();
+    }
+
+    private TestServiceConnection bindToTestServiceAndGetConnection() throws Exception {
         final TestServiceConnection connection = new TestServiceConnection();
         final Intent intent = new Intent().setComponent(
                 new ComponentName(TEST_APP_PKG, TEST_APP_CLASS_SERVICE));
         mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
-        return ITestReceiver.Stub.asInterface(connection.getService());
+        return connection;
     }
 
     /**
@@ -1900,6 +2001,14 @@
                     TimeUnit.SECONDS);
             return service;
         }
+
+        public ITestReceiver getITestReceiver() throws Exception {
+            return ITestReceiver.Stub.asInterface(getService());
+        }
+
+        public void unbind() {
+            mContext.unbindService(this);
+        }
     }
 
     private void runJobImmediately() throws Exception {
diff --git a/tests/tests/appenumeration/Android.bp b/tests/tests/appenumeration/Android.bp
index 5d510eb..cb6df44 100644
--- a/tests/tests/appenumeration/Android.bp
+++ b/tests/tests/appenumeration/Android.bp
@@ -30,6 +30,7 @@
 	    "androidx.test.ext.junit",
         "hamcrest-library",
 	    "CtsAppEnumerationTestLib",
+	    "cts-install-lib",
     ],
 
     srcs: ["src/**/*.java"],
diff --git a/tests/tests/appenumeration/AndroidManifest.xml b/tests/tests/appenumeration/AndroidManifest.xml
index 9018766..a71c273 100644
--- a/tests/tests/appenumeration/AndroidManifest.xml
+++ b/tests/tests/appenumeration/AndroidManifest.xml
@@ -17,9 +17,16 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="android.appenumeration.cts">
-
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
-
+    <!-- Use specific package names instead of QUERY_ALL_PACKAGES to let AppEnumerationTests
+         live with default visibility -->
+    <queries>
+        <package android:name="android.appenumeration.filters" />
+        <package android:name="android.appenumeration.noapi" />
+        <package android:name="android.appenumeration.noapi.shareduid" />
+        <package android:name="android.appenumeration.queries.activity.action" />
+        <package android:name="android.appenumeration.queries.nothing" />
+        <package android:name="android.appenumeration.stub" />
+    </queries>
     <application>
       <uses-library android:name="android.test.runner" />
     </application>
diff --git a/tests/tests/appenumeration/AndroidTest.xml b/tests/tests/appenumeration/AndroidTest.xml
index d73484d..f2cd10d 100644
--- a/tests/tests/appenumeration/AndroidTest.xml
+++ b/tests/tests/appenumeration/AndroidTest.xml
@@ -35,6 +35,7 @@
         <option name="test-file-name" value="CtsAppEnumerationDocumentsActivityTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationShareActivityTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationWebActivityTarget.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationPrefixWildcardWebActivityTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationBrowserActivityTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationBrowserWildcardActivityTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationSharedUidSource.apk" />
@@ -52,6 +53,7 @@
         <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAuthority.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesUnexportedProviderViaAction.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesPackage.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesPackageHasProvider.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesNothingTargetsQ.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesNothingHasPermission.apk" />
         <option name="test-file-name" value="CtsAppEnumerationQueriesNothingUsesLibrary.apk" />
@@ -68,6 +70,8 @@
         <option name="test-file-name" value="CtsAppEnumerationAppWidgetProviderTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationAppWidgetProviderSharedUidTarget.apk" />
         <option name="test-file-name" value="CtsAppEnumerationPreferredActivityTarget.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk" />
+        <option name="test-file-name" value="CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -96,6 +100,8 @@
         <option name="push" value="CtsAppEnumerationStub.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationStub.apk" />
         <option name="push" value="CtsAppEnumerationFilters.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationFilters.apk" />
         <option name="push" value="CtsAppEnumerationQueriesNothingSeesInstaller.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingSeesInstaller.apk" />
+        <option name="push" value="CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk" />
+        <option name="push" value="CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk->/data/local/tmp/cts/appenumeration/CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/appenumeration/OWNERS b/tests/tests/appenumeration/OWNERS
index 8a44fb2..d1eff73 100644
--- a/tests/tests/appenumeration/OWNERS
+++ b/tests/tests/appenumeration/OWNERS
@@ -2,4 +2,4 @@
 patb@google.com
 toddke@google.com
 chiuwinson@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/tests/tests/appenumeration/app/source/Android.bp b/tests/tests/appenumeration/app/source/Android.bp
index deee2f4..2f88d57 100644
--- a/tests/tests/appenumeration/app/source/Android.bp
+++ b/tests/tests/appenumeration/app/source/Android.bp
@@ -18,6 +18,7 @@
 
 java_defaults {
     name: "CtsAppEnumerationQueriesDefaults",
+    defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
     static_libs: ["CtsAppEnumerationTestLib"],
     sdk_version: "test_current",
@@ -57,6 +58,28 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingReceivesPersistableUri",
+    manifest: "AndroidManifest-queriesNothing-receivesPersistableUri.xml",
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationQueriesNothingReceivesNonPersistableUri",
+    manifest: "AndroidManifest-queriesNothing-receivesNonPersistableUri.xml",
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
     name: "CtsAppEnumerationQueriesNothingSeesInstaller",
     manifest: "AndroidManifest-queriesNothing-seesInstaller.xml",
     defaults: ["CtsAppEnumerationQueriesDefaults"],
@@ -167,6 +190,17 @@
 }
 
 android_test_helper_app {
+    name: "CtsAppEnumerationQueriesPackageHasProvider",
+    manifest: "AndroidManifest-queriesPackage-hasProvider.xml",
+    defaults: ["CtsAppEnumerationQueriesDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
     name: "CtsAppEnumerationQueriesNothingTargetsQ",
     manifest: "AndroidManifest-queriesNothing-targetsQ.xml",
     defaults: ["CtsAppEnumerationQueriesDefaults"],
@@ -242,6 +276,7 @@
         "general-tests",
     ],
 }
+
 android_test_helper_app {
     name: "CtsAppEnumerationWildcardBrowsableActivitySource",
     manifest: "AndroidManifest-queriesWildcard-browsableActivity.xml",
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
index 6641c9a..73bd8f3 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesActivityAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
index 7e84769..5a8933e 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasPermission.xml
@@ -22,7 +22,7 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
         <provider android:name="android.appenumeration.cts.query.TestProvider"
                   android:exported="true"
@@ -32,5 +32,9 @@
                   android:readPermission="android.appenumeration.queries.nothing.haspermission.READ"
                   android:grantUriPermissions="true"
                   android:authorities="android.appenumeration.queries.nothing.haspermission2" />
+        <provider android:name="android.appenumeration.cts.query.TestProvider"
+                  android:exported="false"
+                  android:grantUriPermissions="true"
+                  android:authorities="android.appenumeration.queries.nothing.haspermission3" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
index d046e70..2cf2f6a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-hasProvider.xml
@@ -19,7 +19,7 @@
     package="android.appenumeration.queries.nothing.hasprovider">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
         <provider android:name="android.appenumeration.cts.query.TestProvider"
                   android:authorities="android.appenumeration.queries.nothing.hasprovider"
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.xml
new file mode 100644
index 0000000..9b64bf2
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesNonPersistableUri.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.appenumeration.queries.nothing.receives.nonpersistable.uri">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
index da644a8..5512d23 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPermissionProtectedUri.xml
@@ -19,7 +19,7 @@
     package="android.appenumeration.queries.nothing.receives.perm.uri">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.xml
new file mode 100644
index 0000000..420056d
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesPersistableUri.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.appenumeration.queries.nothing.receives.persistable.uri">
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
index 18ab7ef..8c7ffd7 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-receivesUri.xml
@@ -19,7 +19,7 @@
     package="android.appenumeration.queries.nothing.receives.uri">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
index 1d5f0bc..bfc855f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-seesInstaller.xml
@@ -19,7 +19,7 @@
     package="android.appenumeration.queries.nothing.sees.installer">
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
index 90db3e6..5f50b62 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-sharedUser.xml
@@ -20,7 +20,7 @@
           android:sharedUserId="android.appenumeration.shareduid">
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
index 60a0c2d..638d9fa 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-targetsQ.xml
@@ -20,7 +20,7 @@
     <uses-sdk android:targetSdkVersion="29" />
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
index 99edef0..3a7ef8a 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesLibrary.xml
@@ -20,7 +20,7 @@
     <application>
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="com.android.cts.ctsshim.shared_library" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
index 87b7738..65dde1f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing-usesOptionalLibrary.xml
@@ -21,7 +21,7 @@
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="com.android.cts.ctsshim.shared_library"
                       android:required="false" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
index e5e160a..3bda428 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesNothing.xml
@@ -19,7 +19,7 @@
     package="android.appenumeration.queries.nothing">
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml
new file mode 100644
index 0000000..2225d1f
--- /dev/null
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage-hasProvider.xml
@@ -0,0 +1,35 @@
+<?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.appenumeration.queries.pkg.hasprovider">
+
+    <queries>
+        <package android:name="android.appenumeration.noapi" />
+    </queries>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
+        <provider android:name="android.appenumeration.cts.query.TestProvider"
+                  android:authorities="android.appenumeration.queries.pkg.hasprovider"
+                  android:grantUriPermissions="true"
+                  android:exported="false" >
+        </provider>
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
index 4588f53..956fc04 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesPackage.xml
@@ -24,11 +24,12 @@
         <package android:name="android.appenumeration.syncadapter" />
         <package android:name="android.appenumeration.appwidgetprovider" />
         <package android:name="android.appenumeration.preferred.activity" />
+        <package android:name="com.android.cts.install.lib.testapp.A" />
     </queries>
 
     <application android:debuggable="true">
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
index 2f1cf69..6d7567f 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
index 7fb4191..9a17108 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesProviderAuthority.xml
@@ -24,7 +24,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
index dab3e2e..78fd937 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesServiceAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
index aabb703..a930b3c 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedActivityAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
index 72dfa6b..40732bf 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
index 09755f0..d92cfbd 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedProviderAuthority.xml
@@ -24,7 +24,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
index d1fdd13..32b6d13 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesUnexportedServiceAction.xml
@@ -26,7 +26,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
index b08cc12..823dcd0 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browsableActivity.xml
@@ -28,7 +28,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
index b6f96aa..6230245 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-browserActivity.xml
@@ -33,7 +33,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
index 80138c5..c3e754b 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-contactsActivity.xml
@@ -29,7 +29,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
index 0f7e971..48191b1 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-documentEditorActivity.xml
@@ -28,7 +28,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
index e9a051b..ceed8e9 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-shareActivity.xml
@@ -27,7 +27,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
index 74412cc..9f0f2d2 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcard-webActivity.xml
@@ -33,7 +33,7 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
index 64e3af3..c538cc4 100644
--- a/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
+++ b/tests/tests/appenumeration/app/source/AndroidManifest-queriesWildcardAction.xml
@@ -24,7 +24,7 @@
     </queries>
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="android.appenumeration.cts.query.TestActivity"
+        <activity android:name="android.appenumeration.cts.TestActivity"
                   android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java b/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
deleted file mode 100644
index bcbbc53..0000000
--- a/tests/tests/appenumeration/app/source/src/android/appenumeration/cts/query/TestActivity.java
+++ /dev/null
@@ -1,685 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.appenumeration.cts.query;
-
-import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
-import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
-import static android.appenumeration.cts.Constants.ACTION_GET_NAMES_FOR_UIDS;
-import static android.appenumeration.cts.Constants.ACTION_GET_NAME_FOR_UID;
-import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
-import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
-import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
-import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
-import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
-import static android.appenumeration.cts.Constants.ACTION_SEND_RESULT;
-import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
-import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
-import static android.appenumeration.cts.Constants.ACTION_START_SENDER_FOR_RESULT;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_AVAILABLE;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_SUSPENDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNAVAILABLE;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNSUSPENDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
-import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
-import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
-import static android.appenumeration.cts.Constants.EXTRA_CERT;
-import static android.appenumeration.cts.Constants.EXTRA_DATA;
-import static android.appenumeration.cts.Constants.EXTRA_ERROR;
-import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
-import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
-import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
-import static android.content.Intent.EXTRA_COMPONENT_NAME;
-import static android.content.Intent.EXTRA_PACKAGES;
-import static android.content.Intent.EXTRA_RETURN_RESULT;
-import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
-import static android.os.Process.INVALID_UID;
-
-import android.app.Activity;
-import android.app.PendingIntent;
-import android.appenumeration.cts.Constants;
-import android.appenumeration.cts.MissingBroadcastException;
-import android.appwidget.AppWidgetManager;
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.ActivityNotFoundException;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.ServiceConnection;
-import android.content.SyncAdapterType;
-import android.content.pm.LauncherApps;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.SharedLibraryInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Parcelable;
-import android.os.PatternMatcher;
-import android.os.Process;
-import android.os.RemoteCallback;
-import android.os.UserHandle;
-import android.util.SparseArray;
-import android.view.accessibility.AccessibilityManager;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Collectors;
-
-public class TestActivity extends Activity {
-
-    private final static long TIMEOUT_MS = 3000;
-
-    /**
-     * Extending the timeout time of non broadcast receivers, avoid not
-     * receiving callbacks in time on some common low-end platforms and
-     * do not affect the situation that callback can be received in advance.
-     */
-    private final static long EXTENDED_TIMEOUT_MS = 5000;
-
-    SparseArray<RemoteCallback> callbacks = new SparseArray<>();
-
-    private Handler mainHandler;
-    private Handler backgroundHandler;
-    private HandlerThread backgroundThread;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        mainHandler = new Handler(getMainLooper());
-        backgroundThread = new HandlerThread("testBackground");
-        backgroundThread.start();
-        backgroundHandler = new Handler(backgroundThread.getLooper());
-        super.onCreate(savedInstanceState);
-        handleIntent(getIntent());
-        onCommandReady(getIntent());
-    }
-
-    @Override
-    protected void onDestroy() {
-        backgroundThread.quitSafely();
-        super.onDestroy();
-    }
-
-    private void handleIntent(Intent intent) {
-        RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
-        try {
-            final String action = intent.getAction();
-            final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
-            if (ACTION_GET_PACKAGE_INFO.equals(action)) {
-                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                sendPackageInfo(remoteCallback, packageName);
-            } else if (ACTION_GET_PACKAGES_FOR_UID.equals(action)) {
-                final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                sendPackagesForUid(remoteCallback, uid);
-            } else if (ACTION_GET_NAME_FOR_UID.equals(action)) {
-                final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                sendNameForUid(remoteCallback, uid);
-            } else if (ACTION_GET_NAMES_FOR_UIDS.equals(action)) {
-                final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                sendNamesForUids(remoteCallback, uid);
-            } else if (ACTION_CHECK_SIGNATURES.equals(action)) {
-                final int uid1 = getPackageManager().getApplicationInfo(
-                        getPackageName(), /* flags */ 0).uid;
-                final int uid2 = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                sendCheckSignatures(remoteCallback, uid1, uid2);
-            } else if (ACTION_HAS_SIGNING_CERTIFICATE.equals(action)) {
-                final int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
-                final byte[] cert = intent.getBundleExtra(EXTRA_DATA).getByteArray(EXTRA_CERT);
-                sendHasSigningCertificate(remoteCallback, uid, cert, CERT_INPUT_RAW_X509);
-            } else if (ACTION_START_FOR_RESULT.equals(action)) {
-                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                int requestCode = RESULT_FIRST_USER + callbacks.size();
-                callbacks.put(requestCode, remoteCallback);
-                startActivityForResult(
-                        new Intent(ACTION_SEND_RESULT).setComponent(
-                                new ComponentName(packageName, getClass().getCanonicalName())),
-                        requestCode);
-                // don't send anything... await result callback
-            } else if (ACTION_SEND_RESULT.equals(action)) {
-                try {
-                    setResult(RESULT_OK,
-                            getIntent().putExtra(
-                                    Intent.EXTRA_RETURN_RESULT,
-                                    getPackageManager().getPackageInfo(getCallingPackage(), 0)));
-                } catch (PackageManager.NameNotFoundException e) {
-                    setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
-                }
-                finish();
-            } else if (ACTION_QUERY_ACTIVITIES.equals(action)) {
-                sendQueryIntentActivities(remoteCallback, queryIntent);
-            } else if (ACTION_QUERY_SERVICES.equals(action)) {
-                sendQueryIntentServices(remoteCallback, queryIntent);
-            } else if (ACTION_QUERY_PROVIDERS.equals(action)) {
-                sendQueryIntentProviders(remoteCallback, queryIntent);
-            } else if (ACTION_START_DIRECTLY.equals(action)) {
-                try {
-                    startActivity(queryIntent);
-                    remoteCallback.sendResult(new Bundle());
-                } catch (ActivityNotFoundException e) {
-                    sendError(remoteCallback, e);
-                }
-                finish();
-            } else if (ACTION_JUST_FINISH.equals(action)) {
-                finish();
-            } else if (ACTION_GET_INSTALLED_PACKAGES.equals(action)) {
-                sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra(EXTRA_FLAGS, 0));
-            } else if (ACTION_START_SENDER_FOR_RESULT.equals(action)) {
-                PendingIntent pendingIntent = intent.getParcelableExtra("pendingIntent");
-                int requestCode = RESULT_FIRST_USER + callbacks.size();
-                callbacks.put(requestCode, remoteCallback);
-                try {
-                    startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null,
-                            0, 0, 0);
-                } catch (IntentSender.SendIntentException e) {
-                    sendError(remoteCallback, e);
-                }
-            } else if (Constants.ACTION_AWAIT_PACKAGE_REMOVED.equals(action)) {
-                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                awaitPackageBroadcast(
-                        remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, TIMEOUT_MS);
-            } else if (Constants.ACTION_AWAIT_PACKAGE_ADDED.equals(action)) {
-                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                awaitPackageBroadcast(
-                        remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, TIMEOUT_MS);
-            } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
-                final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                queryResolverForVisiblePackages(remoteCallback, authority);
-            } else if (Constants.ACTION_BIND_SERVICE.equals(action)) {
-                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                bindService(remoteCallback, packageName);
-            } else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
-                sendSyncAdapterTypes(remoteCallback);
-            } else if (Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS.equals(action)) {
-                sendInstalledAppWidgetProviders(remoteCallback);
-            } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
-                final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
-                        .getStringArray(EXTRA_PACKAGES);
-                awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
-                        Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
-            } else if (Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED.equals(action)) {
-                final String componentName = intent.getBundleExtra(EXTRA_DATA)
-                        .getString(EXTRA_COMPONENT_NAME);
-                sendIsActivityEnabled(remoteCallback, ComponentName.unflattenFromString(
-                        componentName));
-            } else if (Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY.equals(action)) {
-                final String authority = intent.getBundleExtra(EXTRA_DATA)
-                        .getString(EXTRA_AUTHORITY);
-                final int userId = intent.getBundleExtra(EXTRA_DATA)
-                        .getInt(Intent.EXTRA_USER);
-                sendSyncAdapterPackagesForAuthorityAsUser(remoteCallback, authority, userId);
-            } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK.equals(action)) {
-                final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
-                        .getInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
-                awaitLauncherAppsCallback(remoteCallback, expectedEventCode, EXTENDED_TIMEOUT_MS);
-            } else if (Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES.equals(action)) {
-                final String sharedLibName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                sendGetSharedLibraryDependentPackages(remoteCallback, sharedLibName);
-            } else if (Constants.ACTION_GET_PREFERRED_ACTIVITIES.equals(action)) {
-                sendGetPreferredActivities(remoteCallback);
-            } else if (Constants.ACTION_SET_INSTALLER_PACKAGE_NAME.equals(action)) {
-                final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
-                final String installerPackageName = intent.getBundleExtra(EXTRA_DATA)
-                        .getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
-                sendSetInstallerPackageName(remoteCallback, targetPackageName,
-                        installerPackageName);
-            } else if (Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES.equals(
-                    action)) {
-                sendGetInstalledAccessibilityServicePackages(remoteCallback);
-            } else {
-                sendError(remoteCallback, new Exception("unknown action " + action));
-            }
-        } catch (Exception e) {
-            sendError(remoteCallback, e);
-        }
-    }
-
-    private void sendGetInstalledAccessibilityServicePackages(RemoteCallback remoteCallback) {
-        final String[] packages = getSystemService(
-                AccessibilityManager.class).getInstalledAccessibilityServiceList().stream().map(
-                p -> p.getComponentName().getPackageName()).distinct().toArray(String[]::new);
-        final Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, packages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void onCommandReady(Intent intent) {
-        final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
-        if (callback != null) {
-            callback.sendResult(null);
-        }
-    }
-
-    private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
-            String action, long timeoutMs) {
-        final IntentFilter filter = new IntentFilter(action);
-        filter.addDataScheme("package");
-        filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
-        final Object token = new Object();
-        registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final Bundle result = new Bundle();
-                result.putString(EXTRA_DATA, intent.getDataString());
-                remoteCallback.sendResult(result);
-                mainHandler.removeCallbacksAndMessages(token);
-                finish();
-            }
-        }, filter);
-        mainHandler.postDelayed(
-                () -> sendError(remoteCallback,
-                        new MissingBroadcastException(action, timeoutMs)),
-                token, timeoutMs);
-    }
-
-    private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
-            List<String> awaitList, String action, long timeoutMs) {
-        final IntentFilter filter = new IntentFilter(action);
-        final ArrayList<String> suspendedList = new ArrayList<>();
-        final Object token = new Object();
-        final Runnable sendResult = () -> {
-            final Bundle result = new Bundle();
-            result.putStringArray(EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
-            remoteCallback.sendResult(result);
-            finish();
-        };
-        registerReceiver(new BroadcastReceiver() {
-            @Override
-            public void onReceive(Context context, Intent intent) {
-                final Bundle extras = intent.getExtras();
-                final String[] changedList = extras.getStringArray(
-                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
-                suspendedList.addAll(Arrays.stream(changedList).filter(
-                        p -> awaitList.contains(p)).collect(Collectors.toList()));
-                if (suspendedList.size() == awaitList.size()) {
-                    mainHandler.removeCallbacksAndMessages(token);
-                    sendResult.run();
-                }
-            }
-        }, filter);
-        mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
-    }
-
-    private void awaitLauncherAppsCallback(RemoteCallback remoteCallback, int expectedEventCode,
-            long timeoutMs) {
-        final Object token = new Object();
-        final Bundle result = new Bundle();
-        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
-        final LauncherApps.Callback launcherAppsCallback = new LauncherApps.Callback() {
-
-            private void onPackageStateUpdated(String[] packageNames, int resultCode) {
-                if (resultCode != expectedEventCode) {
-                    return;
-                }
-
-                mainHandler.removeCallbacksAndMessages(token);
-                result.putStringArray(EXTRA_PACKAGES, packageNames);
-                result.putInt(EXTRA_FLAGS, resultCode);
-                remoteCallback.sendResult(result);
-
-                launcherApps.unregisterCallback(this);
-                finish();
-            }
-
-            @Override
-            public void onPackageRemoved(String packageName, UserHandle user) {
-                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_REMOVED);
-            }
-
-            @Override
-            public void onPackageAdded(String packageName, UserHandle user) {
-                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_ADDED);
-            }
-
-            @Override
-            public void onPackageChanged(String packageName, UserHandle user) {
-                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_CHANGED);
-            }
-
-            @Override
-            public void onPackagesAvailable(String[] packageNames, UserHandle user,
-                    boolean replacing) {
-                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_AVAILABLE);
-            }
-
-            @Override
-            public void onPackagesUnavailable(String[] packageNames, UserHandle user,
-                    boolean replacing) {
-                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNAVAILABLE);
-            }
-
-            @Override
-            public void onPackagesSuspended(String[] packageNames, UserHandle user) {
-                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_SUSPENDED);
-                super.onPackagesSuspended(packageNames, user);
-            }
-
-            @Override
-            public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
-                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNSUSPENDED);
-                super.onPackagesUnsuspended(packageNames, user);
-            }
-        };
-
-        launcherApps.registerCallback(launcherAppsCallback);
-
-        mainHandler.postDelayed(() -> {
-            result.putStringArray(EXTRA_PACKAGES, new String[]{});
-            result.putInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
-            remoteCallback.sendResult(result);
-
-            launcherApps.unregisterCallback(launcherAppsCallback);
-            finish();
-        }, token, timeoutMs);
-    }
-
-    private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
-        String[] packages =
-                getPackageManager().getInstalledPackages(flags)
-                        .stream().map(p -> p.packageName).distinct().toArray(String[]::new);
-        Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, packages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendQueryIntentActivities(RemoteCallback remoteCallback, Intent queryIntent) {
-        final String[] resolveInfos = getPackageManager().queryIntentActivities(
-                queryIntent, 0 /* flags */).stream()
-                .map(ri -> ri.activityInfo.applicationInfo.packageName)
-                .distinct()
-                .toArray(String[]::new);
-        Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendQueryIntentServices(RemoteCallback remoteCallback, Intent queryIntent) {
-        final String[] resolveInfos = getPackageManager().queryIntentServices(
-                queryIntent, 0 /* flags */).stream()
-                .map(ri -> ri.serviceInfo.applicationInfo.packageName)
-                .distinct()
-                .toArray(String[]::new);
-        Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendQueryIntentProviders(RemoteCallback remoteCallback, Intent queryIntent) {
-        final String[] resolveInfos = getPackageManager().queryIntentContentProviders(
-                queryIntent, 0 /* flags */).stream()
-                .map(ri -> ri.providerInfo.applicationInfo.packageName)
-                .distinct()
-                .toArray(String[]::new);
-        Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void queryResolverForVisiblePackages(RemoteCallback remoteCallback, String authority) {
-        backgroundHandler.post(() -> {
-            Uri queryUri = Uri.parse("content://" + authority + "/test");
-            Cursor query = getContentResolver().query(queryUri, null, null, null, null);
-            if (query == null || !query.moveToFirst()) {
-                sendError(remoteCallback,
-                        new IllegalStateException(
-                                "Query of " + queryUri + " could not be completed"));
-                return;
-            }
-            ArrayList<String> visiblePackages = new ArrayList<>();
-            while (!query.isAfterLast()) {
-                visiblePackages.add(query.getString(0));
-                query.moveToNext();
-            }
-            query.close();
-
-            mainHandler.post(() -> {
-                Bundle result = new Bundle();
-                result.putStringArray(EXTRA_RETURN_RESULT, visiblePackages.toArray(new String[]{}));
-                remoteCallback.sendResult(result);
-                finish();
-            });
-
-        });
-    }
-
-    private void sendError(RemoteCallback remoteCallback, Exception failure) {
-        Bundle result = new Bundle();
-        result.putSerializable(EXTRA_ERROR, failure);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
-        final PackageInfo pi;
-        try {
-            pi = getPackageManager().getPackageInfo(packageName, 0);
-        } catch (PackageManager.NameNotFoundException e) {
-            sendError(remoteCallback, e);
-            return;
-        }
-        Bundle result = new Bundle();
-        result.putParcelable(EXTRA_RETURN_RESULT, pi);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendPackagesForUid(RemoteCallback remoteCallback, int uid) {
-        final String[] packages = getPackageManager().getPackagesForUid(uid);
-        final Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, packages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendNameForUid(RemoteCallback remoteCallback, int uid) {
-        final String name = getPackageManager().getNameForUid(uid);
-        final Bundle result = new Bundle();
-        result.putString(EXTRA_RETURN_RESULT, name);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendNamesForUids(RemoteCallback remoteCallback, int uid) {
-        final String[] names = getPackageManager().getNamesForUids(new int[]{uid});
-        final Bundle result = new Bundle();
-        result.putStringArray(EXTRA_RETURN_RESULT, names);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendCheckSignatures(RemoteCallback remoteCallback, int uid1, int uid2) {
-        final int signatureResult = getPackageManager().checkSignatures(uid1, uid2);
-        final Bundle result = new Bundle();
-        result.putInt(EXTRA_RETURN_RESULT, signatureResult);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendHasSigningCertificate(RemoteCallback remoteCallback, int uid, byte[] cert,
-            int type) {
-        final boolean signatureResult = getPackageManager().hasSigningCertificate(uid, cert, type);
-        final Bundle result = new Bundle();
-        result.putBoolean(EXTRA_RETURN_RESULT, signatureResult);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    /**
-     * Instead of sending a list of package names, this function sends a List of
-     * {@link SyncAdapterType}, since the {@link SyncAdapterType#getPackageName()} is a test api
-     * which can only be invoked in the instrumentation.
-     */
-    private void sendSyncAdapterTypes(RemoteCallback remoteCallback) {
-        final SyncAdapterType[] types = ContentResolver.getSyncAdapterTypes();
-        final ArrayList<Parcelable> parcelables = new ArrayList<>();
-        for (SyncAdapterType type : types) {
-            parcelables.add(type);
-        }
-        final Bundle result = new Bundle();
-        result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendIsActivityEnabled(RemoteCallback remoteCallback, ComponentName componentName) {
-        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
-        final Bundle result = new Bundle();
-        try {
-            result.putBoolean(EXTRA_RETURN_RESULT, launcherApps.isActivityEnabled(componentName,
-                    Process.myUserHandle()));
-        } catch (IllegalArgumentException e) {
-        }
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendInstalledAppWidgetProviders(RemoteCallback remoteCallback) {
-        final AppWidgetManager appWidgetManager = getSystemService(AppWidgetManager.class);
-        final List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
-        final ArrayList<Parcelable> parcelables = new ArrayList<>();
-        for (AppWidgetProviderInfo info : providers) {
-            parcelables.add(info);
-        }
-        final Bundle result = new Bundle();
-        result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendSyncAdapterPackagesForAuthorityAsUser(RemoteCallback remoteCallback,
-            String authority, int userId) {
-        final String[] syncAdapterPackages = ContentResolver
-                .getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
-        final Bundle result = new Bundle();
-        result.putStringArray(Intent.EXTRA_PACKAGES, syncAdapterPackages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendGetSharedLibraryDependentPackages(RemoteCallback remoteCallback,
-            String sharedLibName) {
-        final List<SharedLibraryInfo> sharedLibraryInfos = getPackageManager()
-                .getSharedLibraries(0 /* flags */);
-        SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.stream().filter(
-                info -> sharedLibName.equals(info.getName())).findAny().orElse(null);
-        final String[] dependentPackages = sharedLibraryInfo == null ? null
-                : sharedLibraryInfo.getDependentPackages().stream()
-                        .map(versionedPackage -> versionedPackage.getPackageName())
-                        .distinct().collect(Collectors.toList()).toArray(new String[]{});
-        final Bundle result = new Bundle();
-        result.putStringArray(Intent.EXTRA_PACKAGES, dependentPackages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendGetPreferredActivities(RemoteCallback remoteCallback) {
-        final List<IntentFilter> filters = new ArrayList<>();
-        final List<ComponentName> activities = new ArrayList<>();
-        getPackageManager().getPreferredActivities(filters, activities, null /* packageName*/);
-        final String[] packages = activities.stream()
-                .map(componentName -> componentName.getPackageName()).distinct()
-                .collect(Collectors.toList()).toArray(new String[]{});
-        final Bundle result = new Bundle();
-        result.putStringArray(Intent.EXTRA_PACKAGES, packages);
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void sendSetInstallerPackageName(RemoteCallback remoteCallback,
-            String targetPackageName, String installerPackageName) {
-        try {
-            getPackageManager().setInstallerPackageName(targetPackageName, installerPackageName);
-            remoteCallback.sendResult(null);
-            finish();
-        } catch (Exception e) {
-            sendError(remoteCallback, e);
-        }
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        final RemoteCallback remoteCallback = callbacks.get(requestCode);
-        if (resultCode != RESULT_OK) {
-            Exception e = (Exception) data.getSerializableExtra(EXTRA_ERROR);
-            sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
-            return;
-        }
-        final Bundle result = new Bundle();
-        result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
-        remoteCallback.sendResult(result);
-        finish();
-    }
-
-    private void bindService(RemoteCallback remoteCallback, String packageName) {
-        final String SERVICE_NAME = "android.appenumeration.testapp.DummyService";
-        final Intent intent = new Intent();
-        intent.setClassName(packageName, SERVICE_NAME);
-        final ServiceConnection serviceConnection = new ServiceConnection() {
-            @Override
-            public void onServiceConnected(ComponentName className, IBinder service) {
-                // No-op
-            }
-
-            @Override
-            public void onServiceDisconnected(ComponentName className) {
-                // No-op
-            }
-
-            @Override
-            public void onBindingDied(ComponentName name) {
-                // Remote service die
-                finish();
-            }
-
-            @Override
-            public void onNullBinding(ComponentName name) {
-                // Since the DummyService doesn't implement onBind, it returns null and
-                // onNullBinding would be called. Use postDelayed to keep this service
-                // connection alive for 3 seconds.
-                mainHandler.postDelayed(() -> {
-                    unbindService(this);
-                    finish();
-                }, TIMEOUT_MS);
-            }
-        };
-
-        final boolean bound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
-        final Bundle result = new Bundle();
-        result.putBoolean(EXTRA_RETURN_RESULT, bound);
-        remoteCallback.sendResult(result);
-        // Don't invoke finish() right here if service is bound successfully to keep the service
-        // connection alive since the ServiceRecord would be remove from the ServiceMap once no
-        // client is binding the service.
-        if (!bound) finish();
-    }
-}
diff --git a/tests/tests/appenumeration/app/target/Android.bp b/tests/tests/appenumeration/app/target/Android.bp
index d3d8bd2..8b81354 100644
--- a/tests/tests/appenumeration/app/target/Android.bp
+++ b/tests/tests/appenumeration/app/target/Android.bp
@@ -16,228 +16,209 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
+java_defaults {
+    name: "CtsAppEnumerationTargetDefaults",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+    static_libs: ["CtsAppEnumerationTestLib"],
+    sdk_version: "test_current",
+}
+
 android_test_helper_app {
     name: "CtsAppEnumerationForceQueryable",
     manifest: "AndroidManifest-forceQueryable.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationForceQueryableNormalInstall",
     manifest: "AndroidManifest-forceQueryable-normalInstall.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationFilters",
     manifest: "AndroidManifest-filters.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationNoApi",
     manifest: "AndroidManifest-noapi.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationStub",
     manifest: "AndroidManifest-stub.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationSharedUidTarget",
     manifest: "AndroidManifest-noapi-sharedUser.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationContactsActivityTarget",
     manifest: "AndroidManifest-contactsActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationDocumentsActivityTarget",
     manifest: "AndroidManifest-documentEditorActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationShareActivityTarget",
     manifest: "AndroidManifest-shareActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationWebActivityTarget",
     manifest: "AndroidManifest-webActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
+}
+
+android_test_helper_app {
+    name: "CtsAppEnumerationPrefixWildcardWebActivityTarget",
+    manifest: "AndroidManifest-prefixWildcardWebActivity.xml",
+    defaults: ["CtsAppEnumerationTargetDefaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationBrowserActivityTarget",
     manifest: "AndroidManifest-browserActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationBrowserWildcardActivityTarget",
     manifest: "AndroidManifest-browserWildcardActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationSyncadapterTarget",
     manifest: "AndroidManifest-syncadapter.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationSyncadapterSharedUidTarget",
     manifest: "AndroidManifest-syncadapter-sharedUser.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationAppWidgetProviderTarget",
     manifest: "AndroidManifest-appWidgetProvider.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationAppWidgetProviderSharedUidTarget",
     manifest: "AndroidManifest-appWidgetProvider-sharedUser.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
 }
 
 android_test_helper_app {
     name: "CtsAppEnumerationPreferredActivityTarget",
     manifest: "AndroidManifest-preferredActivity.xml",
-    defaults: ["cts_support_defaults"],
-    srcs: ["src/**/*.java"],
-    resource_dirs: ["res"],
+    defaults: ["CtsAppEnumerationTargetDefaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
     ],
-    sdk_version: "test_current",
-}
\ No newline at end of file
+}
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
index 153894c..0f91979 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider-sharedUser.xml
@@ -20,6 +20,8 @@
           android:sharedUserId="android.appenumeration.shareduid">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
 
         <receiver android:name="android.appenumeration.MockAppWidgetProvider"
                   android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
index 836acda..100f821 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-appWidgetProvider.xml
@@ -19,6 +19,8 @@
     package="android.appenumeration.appwidgetprovider">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
 
         <receiver android:name="android.appenumeration.MockAppWidgetProvider"
                  android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
index f4aecba..8a0cf23 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserActivity.xml
@@ -19,6 +19,8 @@
     package="android.appenumeration.browser.activity">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.WebBrowser"
             android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
index 97b0d9b..c165ba8 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-browserWildcardActivity.xml
@@ -19,6 +19,8 @@
     package="android.appenumeration.browser.wildcard.activity">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.WebBrowser"
             android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
index 0bff9cc..fdd9081 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-contactsActivity.xml
@@ -19,6 +19,8 @@
     package="android.appenumeration.contacts.activity">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.ContactsActivity"
                   android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
index 6795cf7..011f59b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-documentEditorActivity.xml
@@ -19,6 +19,8 @@
      package="android.appenumeration.editor.activity">
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.EditorActivity"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
index eeb3170..9f89655 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-filters.xml
@@ -19,6 +19,8 @@
      package="android.appenumeration.filters">
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.testapp.DummyActivity"
              android:visibleToInstantApps="true"
              android:exported="true">
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
index 2918e37..178b6fa 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable-normalInstall.xml
@@ -21,5 +21,7 @@
         <!-- This app will not be a system app and should be installed as a normal app (not
              forceQueryable) to ensure it's not visible by default -->
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
index 3778b04..e835b52 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-forceQueryable.xml
@@ -21,5 +21,7 @@
         <!-- This app will not be a system app and so must be installed as forceQueryable by the
              test framework -->
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
index 3b5be22..6630f68 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi-sharedUser.xml
@@ -20,5 +20,7 @@
           android:sharedUserId="android.appenumeration.shareduid">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
index fc2835e..ced7705 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-noapi.xml
@@ -19,5 +19,7 @@
     package="android.appenumeration.noapi">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
index fb9e796..8a86825 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-preferredActivity.xml
@@ -19,6 +19,8 @@
           package="android.appenumeration.preferred.activity">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.testapp.DummyActivity"
                   android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml
new file mode 100644
index 0000000..f5bd1aa
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-prefixWildcardWebActivity.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright (C) 2022 The Android Open Source Project
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     package="android.appenumeration.prefix.wildcard.web.activity">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
+        <activity android:name="android.appenumeration.WebActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="http"
+                     android:host="*.appenumeration.android"/>
+                <data android:scheme="https"
+                     android:host="*.appenumeration.android"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
index 148fa29..af9e57b 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-shareActivity.xml
@@ -19,6 +19,8 @@
      package="android.appenumeration.share.activity">
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.ShareActivity"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml b/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
index af8daa4..630358e 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-stub.xml
@@ -19,5 +19,7 @@
           package="android.appenumeration.stub">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
index 158067d..8c8dd09 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter-sharedUser.xml
@@ -20,8 +20,18 @@
           android:sharedUserId="android.appenumeration.shareduid">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
 
-        <service android:name="android.appenumeration.MockSyncAdapterService"
+        <service android:name="android.appenumeration.testapp.MockAuthenticatorService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator" />
+            </intent-filter>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                       android:resource="@xml/authenticator_shareduser" />
+        </service>
+        <service android:name="android.appenumeration.testapp.MockSyncAdapterService"
                  android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
@@ -29,5 +39,9 @@
             <meta-data android:name="android.content.SyncAdapter"
                        android:resource="@xml/syncadapter_shareduser"/>
         </service>
+
+        <provider android:name="android.appenumeration.testapp.DummyProvider"
+                  android:authorities="android.appenumeration.syncadapter.shareduid.authority"
+                  android:exported="false" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
index f1177df..4529f10 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-syncadapter.xml
@@ -19,8 +19,18 @@
     package="android.appenumeration.syncadapter">
     <application>
         <uses-library android:name="android.test.runner" />
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
 
-        <service android:name="android.appenumeration.MockSyncAdapterService"
+        <service android:name="android.appenumeration.testapp.MockAuthenticatorService"
+                 android:exported="false">
+            <intent-filter>
+                <action android:name="android.accounts.AccountAuthenticator" />
+            </intent-filter>
+            <meta-data android:name="android.accounts.AccountAuthenticator"
+                       android:resource="@xml/authenticator" />
+        </service>
+        <service android:name="android.appenumeration.testapp.MockSyncAdapterService"
                  android:exported="true">
             <intent-filter>
                 <action android:name="android.content.SyncAdapter"/>
@@ -28,5 +38,9 @@
             <meta-data android:name="android.content.SyncAdapter"
                        android:resource="@xml/syncadapter"/>
         </service>
+
+        <provider android:name="android.appenumeration.testapp.DummyProvider"
+                  android:authorities="android.appenumeration.syncadapter.authority"
+                  android:exported="false" />
     </application>
 </manifest>
diff --git a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
index 31fe275..bbb5275 100644
--- a/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
+++ b/tests/tests/appenumeration/app/target/AndroidManifest-webActivity.xml
@@ -19,6 +19,8 @@
      package="android.appenumeration.web.activity">
     <application>
         <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.appenumeration.cts.TestActivity"
+                  android:exported="true" />
         <activity android:name="android.appenumeration.WebActivity"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/appenumeration/app/target/res/xml/authenticator.xml b/tests/tests/appenumeration/app/target/res/xml/authenticator.xml
new file mode 100644
index 0000000..d3853b7
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/res/xml/authenticator.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accountType="android.appenumeration.account.type"
+    android:label="Mock Account" />
diff --git a/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml b/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml
new file mode 100644
index 0000000..8c7936c
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/res/xml/authenticator_shareduser.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
+    android:accountType="android.appenumeration.shareduid.account.type"
+    android:label="Mock Shared User Account" />
diff --git a/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml b/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
index fd47011..2ca7cbe 100644
--- a/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
+++ b/tests/tests/appenumeration/app/target/res/xml/syncadapter_shareduser.xml
@@ -15,5 +15,5 @@
 -->
 <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
     android:contentAuthority="android.appenumeration.syncadapter.shareduid.authority"
-    android:accountType="android.appenumeration.account.type"
+    android:accountType="android.appenumeration.shareduid.account.type"
 />
diff --git a/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java
new file mode 100644
index 0000000..250c6f2
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/DummyProvider.java
@@ -0,0 +1,55 @@
+/*
+ * 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.appenumeration.testapp;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class DummyProvider extends ContentProvider {
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @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/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java
new file mode 100644
index 0000000..45600ea
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockAuthenticatorService.java
@@ -0,0 +1,88 @@
+/*
+ * 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.appenumeration.testapp;
+
+import android.accounts.AbstractAccountAuthenticator;
+import android.accounts.Account;
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.NetworkErrorException;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class MockAuthenticatorService extends Service {
+
+    private static Authenticator sInstance;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (sInstance == null) {
+            sInstance = new Authenticator(getApplicationContext());
+        }
+        return sInstance.getIBinder();
+    }
+
+    private static class Authenticator extends AbstractAccountAuthenticator {
+
+        public Authenticator(Context context) {
+            super(context);
+        }
+
+        @Override
+        public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
+            return null;
+        }
+
+        @Override
+        public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] requiredFeatures, Bundle options)
+                throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
+                Bundle options) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public String getAuthTokenLabel(String authTokenType) {
+            return null;
+        }
+
+        @Override
+        public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle options) throws NetworkErrorException {
+            return null;
+        }
+
+        @Override
+        public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
+                String[] features) throws NetworkErrorException {
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java
new file mode 100644
index 0000000..41fb62d
--- /dev/null
+++ b/tests/tests/appenumeration/app/target/src/android/appenumeration/testapp/MockSyncAdapterService.java
@@ -0,0 +1,52 @@
+/*
+ * 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.appenumeration.testapp;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.IBinder;
+
+public class MockSyncAdapterService extends Service {
+
+    private static SyncAdapter sInstance;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (sInstance == null) {
+            sInstance = new SyncAdapter(getApplicationContext());
+        }
+        return sInstance.getSyncAdapterBinder();
+    }
+
+    static class SyncAdapter extends AbstractThreadedSyncAdapter {
+
+        public SyncAdapter(Context context) {
+            super(context, false /* autoInitialize */);
+        }
+
+        @Override
+        public void onPerformSync(Account account, Bundle extras, String authority,
+                ContentProviderClient provider, SyncResult syncResult) {
+        }
+    }
+}
diff --git a/tests/tests/appenumeration/lib/Android.bp b/tests/tests/appenumeration/lib/Android.bp
index c608d2c..841f404 100644
--- a/tests/tests/appenumeration/lib/Android.bp
+++ b/tests/tests/appenumeration/lib/Android.bp
@@ -18,10 +18,6 @@
 
 java_library {
     name: "CtsAppEnumerationTestLib",
-    srcs: ["src/**/*.java"],
-}
-
-java_library_host {
-    name: "CtsAppEnumerationTestLibHost",
+    defaults: ["cts_support_defaults"],
     srcs: ["src/**/*.java"],
 }
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
index c93016b..3910691 100644
--- a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/Constants.java
@@ -22,6 +22,8 @@
 
     /** A package that queries for {@link #TARGET_NO_API} package */
     public static final String QUERIES_PACKAGE = PKG_BASE + "queries.pkg";
+    /** A package has a provider that queries for {@link #TARGET_NO_API} package */
+    public static final String QUERIES_PACKAGE_PROVIDER = PKG_BASE + "queries.pkg.hasprovider";
     /** Queries for the unexported authority in {@link #TARGET_FILTERS} provider */
     public static final String QUERIES_UNEXPORTED_PROVIDER_AUTH =
             PKG_BASE + "queries.provider.authority.unexported";
@@ -55,6 +57,10 @@
             PKG_BASE + "queries.nothing.receives.uri";
     public static final String QUERIES_NOTHING_RECEIVES_PERM_URI =
             PKG_BASE + "queries.nothing.receives.perm.uri";
+    public static final String QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI =
+            PKG_BASE + "queries.nothing.receives.persistable.uri";
+    public static final String QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI =
+            PKG_BASE + "queries.nothing.receives.nonpersistable.uri";
     /** Another package that has no queries tag or permission to query any specific packages */
     public static final String QUERIES_NOTHING_SEES_INSTALLER =
             PKG_BASE + "queries.nothing.sees.installer";
@@ -104,6 +110,9 @@
     public static final String TARGET_SHARE = PKG_BASE + "share.activity";
     /** A package that offers an activity that handles browsable web intents for a specific host */
     public static final String TARGET_WEB = PKG_BASE + "web.activity";
+    /** A package that offers an activity acts as a browser, but use a prefix wildcard for host */
+    public static final String TARGET_PREFIX_WILDCARD_WEB =
+            PKG_BASE + "prefix.wildcard.web.activity";
     /** A package that offers an activity acts as a browser with host undefined */
     public static final String TARGET_BROWSER = PKG_BASE + "browser.activity";
     /** A package that offers an activity acts as a browser, but uses a wildcard for host */
@@ -129,6 +138,10 @@
     public static final String TARGET_FILTERS_APK = BASE_PATH + "CtsAppEnumerationFilters.apk";
     public static final String QUERIES_NOTHING_SEES_INSTALLER_APK =
             BASE_PATH + "CtsAppEnumerationQueriesNothingSeesInstaller.apk";
+    public static final String QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK =
+            BASE_PATH + "CtsAppEnumerationQueriesNothingReceivesPersistableUri.apk";
+    public static final String QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK =
+            BASE_PATH + "CtsAppEnumerationQueriesNothingReceivesNonPersistableUri.apk";
 
     public static final String[] ALL_QUERIES_TARGETING_R_PACKAGES = {
             QUERIES_NOTHING,
@@ -149,7 +162,7 @@
             QUERIES_WILDCARD_WEB,
     };
 
-    public static final String ACTIVITY_CLASS_TEST = PKG_BASE + "cts.query.TestActivity";
+    public static final String ACTIVITY_CLASS_TEST = PKG_BASE + "cts.TestActivity";
     public static final String ACTIVITY_CLASS_DUMMY_ACTIVITY = PKG_BASE + "testapp.DummyActivity";
 
     public static final String ACTION_MANIFEST_ACTIVITY = PKG_BASE + "action.ACTIVITY";
@@ -173,7 +186,10 @@
             PKG_BASE + "cts.action.AWAIT_PACKAGE_REMOVED";
     public static final String ACTION_AWAIT_PACKAGE_ADDED =
             PKG_BASE + "cts.action.AWAIT_PACKAGE_ADDED";
-
+    public static final String ACTION_AWAIT_PACKAGE_FULLY_REMOVED =
+            PKG_BASE + "cts.action.AWAIT_PACKAGE_FULLY_REMOVED";
+    public static final String ACTION_AWAIT_PACKAGE_DATA_CLEARED =
+            PKG_BASE + "cts.action.AWAIT_PACKAGE_DATA_CLEARED";
     public static final String ACTION_QUERY_ACTIVITIES =
             PKG_BASE + "cts.action.QUERY_INTENT_ACTIVITIES";
     public static final String ACTION_QUERY_SERVICES =
@@ -193,10 +209,14 @@
             PKG_BASE + "cts.action.GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY";
     public static final String ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS =
             PKG_BASE + "cts.action.GET_INSTALLED_APPWIDGET_PROVIDERS";
+    public static final String ACTION_REQUEST_SYNC_AND_AWAIT_STATUS =
+            PKG_BASE + "cts.action.REQUEST_SYNC_AND_AWAIT_STATUS";
     public static final String ACTION_AWAIT_PACKAGES_SUSPENDED =
             PKG_BASE + "cts.action.AWAIT_PACKAGES_SUSPENDED";
     public static final String ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED =
             PKG_BASE + "cts.action.LAUNCHER_APPS_IS_ACTIVITY_ENABLED";
+    public static final String ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS =
+            PKG_BASE + "cts.action.LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS";
     public static final String ACTION_AWAIT_LAUNCHER_APPS_CALLBACK =
             PKG_BASE + "cts.action.AWAIT_LAUNCHER_APPS_CALLBACK";
     public static final String ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES =
@@ -207,6 +227,35 @@
             PKG_BASE + "cts.action.SET_INSTALLER_PACKAGE_NAME";
     public static final String ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES =
             PKG_BASE + "cts.action.GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES";
+    public static final String ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS =
+            PKG_BASE + "cts.action.LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS";
+    public static final String ACTION_CHECK_URI_PERMISSION =
+            PKG_BASE + "cts.action.CHECK_URI_PERMISSION";
+    public static final String ACTION_TAKE_PERSISTABLE_URI_PERMISSION =
+            PKG_BASE + "cts.action.TAKE_PERSISTABLE_URI_PERMISSION";
+    public static final String ACTION_CAN_PACKAGE_QUERY =
+            PKG_BASE + "cts.action.CAN_PACKAGE_QUERY";
+    public static final String ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS =
+            PKG_BASE + "cts.action.GET_ALL_PACKAGE_INSTALLER_SESSIONS";
+    public static final String ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK =
+            PKG_BASE + "cts.action.AWAIT_LAUNCHER_APPS_SESSION_CALLBACK";
+    public static final String ACTION_GET_SESSION_INFO =
+            PKG_BASE + "cts.action.GET_SESSION_INFO";
+    public static final String ACTION_GET_STAGED_SESSIONS =
+            PKG_BASE + "cts.action.GET_STAGED_SESSIONS";
+    public static final String ACTION_GET_ALL_SESSIONS =
+            PKG_BASE + "cts.action.GET_ALL_SESSIONS";
+    public static final String ACTION_PENDING_INTENT_GET_ACTIVITY =
+            PKG_BASE + "cts.action.PENDING_INTENT_GET_ACTIVITY";
+    public static final String ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE =
+            PKG_BASE + "cts.action.PENDING_INTENT_GET_CREATOR_PACKAGE";
+    public static final String ACTION_CHECK_PACKAGE =
+            PKG_BASE + "cts.action.CHECK_PACKAGE";
+    public static final String ACTION_GRANT_URI_PERMISSION =
+            PKG_BASE + "cts.action.GRANT_URI_PERMISSION";
+    public static final String ACTION_REVOKE_URI_PERMISSION =
+            PKG_BASE + "cts.action.REVOKE_URI_PERMISSION";
+
     public static final String EXTRA_REMOTE_CALLBACK = "remoteCallback";
     public static final String EXTRA_REMOTE_READY_CALLBACK = "remoteReadyCallback";
     public static final String EXTRA_ERROR = "error";
@@ -214,6 +263,9 @@
     public static final String EXTRA_DATA = "data";
     public static final String EXTRA_CERT = "cert";
     public static final String EXTRA_AUTHORITY = "authority";
+    public static final String EXTRA_ACCOUNT = "account";
+    public static final String EXTRA_ID = "id";
+    public static final String EXTRA_PENDING_INTENT = "pendingIntent";
 
     public static final int CALLBACK_EVENT_INVALID = -1;
     public static final int CALLBACK_EVENT_PACKAGE_ADDED = 0;
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.java
new file mode 100644
index 0000000..1efe1b7
--- /dev/null
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/MissingCallbackException.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 android.appenumeration.cts;
+
+public class MissingCallbackException extends Exception {
+
+    public MissingCallbackException(String action, long timeoutMs) {
+        super("The callback of " + action + " was not received within " + timeoutMs + "ms");
+    }
+}
diff --git a/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java
new file mode 100644
index 0000000..3c315a5
--- /dev/null
+++ b/tests/tests/appenumeration/lib/src/android/appenumeration/cts/TestActivity.java
@@ -0,0 +1,972 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appenumeration.cts;
+
+import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
+import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_NAMES_FOR_UIDS;
+import static android.appenumeration.cts.Constants.ACTION_GET_NAME_FOR_UID;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
+import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
+import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
+import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
+import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_SEND_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
+import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_START_SENDER_FOR_RESULT;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_AVAILABLE;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_SUSPENDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNAVAILABLE;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGES_UNSUSPENDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
+import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
+import static android.appenumeration.cts.Constants.EXTRA_ACCOUNT;
+import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
+import static android.appenumeration.cts.Constants.EXTRA_CERT;
+import static android.appenumeration.cts.Constants.EXTRA_DATA;
+import static android.appenumeration.cts.Constants.EXTRA_ERROR;
+import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_ID;
+import static android.appenumeration.cts.Constants.EXTRA_PENDING_INTENT;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
+import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
+import static android.content.Intent.EXTRA_COMPONENT_NAME;
+import static android.content.Intent.EXTRA_PACKAGES;
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+import static android.content.Intent.EXTRA_UID;
+import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
+import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
+
+import android.accounts.Account;
+import android.app.Activity;
+import android.app.AppOpsManager;
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
+import android.content.SyncAdapterType;
+import android.content.SyncStatusObserver;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller.SessionCallback;
+import android.content.pm.PackageInstaller.SessionInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.UserHandle;
+import android.util.SparseArray;
+import android.view.accessibility.AccessibilityManager;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ *  A test activity running in the query and target applications.
+ */
+public class TestActivity extends Activity {
+
+    private final static long TIMEOUT_MS = 3000;
+
+    /**
+     * Extending the timeout time of non broadcast receivers, avoid not
+     * receiving callbacks in time on some common low-end platforms and
+     * do not affect the situation that callback can be received in advance.
+     */
+    private final static long EXTENDED_TIMEOUT_MS = 5000;
+
+    SparseArray<RemoteCallback> callbacks = new SparseArray<>();
+
+    private Handler mainHandler;
+    private Handler backgroundHandler;
+    private HandlerThread backgroundThread;
+    private Object syncStatusHandle;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        mainHandler = new Handler(getMainLooper());
+        backgroundThread = new HandlerThread("testBackground");
+        backgroundThread.start();
+        backgroundHandler = new Handler(backgroundThread.getLooper());
+        super.onCreate(savedInstanceState);
+        handleIntent(getIntent());
+        onCommandReady(getIntent());
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (syncStatusHandle != null) {
+            ContentResolver.removeStatusChangeListener(syncStatusHandle);
+        }
+        backgroundThread.quitSafely();
+        super.onDestroy();
+    }
+
+    private void handleIntent(Intent intent) {
+        RemoteCallback remoteCallback = intent.getParcelableExtra(EXTRA_REMOTE_CALLBACK);
+        try {
+            final String action = intent.getAction();
+            final Intent queryIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT);
+            if (ACTION_GET_PACKAGE_INFO.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                sendPackageInfo(remoteCallback, packageName);
+            } else if (ACTION_GET_PACKAGES_FOR_UID.equals(action)) {
+                final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                sendPackagesForUid(remoteCallback, uid);
+            } else if (ACTION_GET_NAME_FOR_UID.equals(action)) {
+                final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                sendNameForUid(remoteCallback, uid);
+            } else if (ACTION_GET_NAMES_FOR_UIDS.equals(action)) {
+                final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                sendNamesForUids(remoteCallback, uid);
+            } else if (ACTION_CHECK_SIGNATURES.equals(action)) {
+                final int uid1 = getPackageManager().getApplicationInfo(
+                        getPackageName(), /* flags */ 0).uid;
+                final int uid2 = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                sendCheckSignatures(remoteCallback, uid1, uid2);
+            } else if (ACTION_HAS_SIGNING_CERTIFICATE.equals(action)) {
+                final int uid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                final byte[] cert = intent.getBundleExtra(EXTRA_DATA).getByteArray(EXTRA_CERT);
+                sendHasSigningCertificate(remoteCallback, uid, cert, CERT_INPUT_RAW_X509);
+            } else if (ACTION_START_FOR_RESULT.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                int requestCode = RESULT_FIRST_USER + callbacks.size();
+                callbacks.put(requestCode, remoteCallback);
+                startActivityForResult(
+                        new Intent(ACTION_SEND_RESULT).setComponent(
+                                new ComponentName(packageName, getClass().getCanonicalName())),
+                        requestCode);
+                // don't send anything... await result callback
+            } else if (ACTION_SEND_RESULT.equals(action)) {
+                try {
+                    setResult(RESULT_OK,
+                            getIntent().putExtra(
+                                    Intent.EXTRA_RETURN_RESULT,
+                                    getPackageManager().getPackageInfo(getCallingPackage(), 0)));
+                } catch (PackageManager.NameNotFoundException e) {
+                    setResult(RESULT_FIRST_USER, new Intent().putExtra("error", e));
+                }
+                finish();
+            } else if (ACTION_QUERY_ACTIVITIES.equals(action)) {
+                sendQueryIntentActivities(remoteCallback, queryIntent);
+            } else if (ACTION_QUERY_SERVICES.equals(action)) {
+                sendQueryIntentServices(remoteCallback, queryIntent);
+            } else if (ACTION_QUERY_PROVIDERS.equals(action)) {
+                sendQueryIntentProviders(remoteCallback, queryIntent);
+            } else if (ACTION_START_DIRECTLY.equals(action)) {
+                try {
+                    startActivity(queryIntent);
+                    remoteCallback.sendResult(new Bundle());
+                } catch (ActivityNotFoundException e) {
+                    sendError(remoteCallback, e);
+                }
+                finish();
+            } else if (ACTION_JUST_FINISH.equals(action)) {
+                finish();
+            } else if (ACTION_GET_INSTALLED_PACKAGES.equals(action)) {
+                sendGetInstalledPackages(remoteCallback, queryIntent.getIntExtra(EXTRA_FLAGS, 0));
+            } else if (ACTION_START_SENDER_FOR_RESULT.equals(action)) {
+                PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
+                int requestCode = RESULT_FIRST_USER + callbacks.size();
+                callbacks.put(requestCode, remoteCallback);
+                try {
+                    startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null,
+                            0, 0, 0);
+                } catch (IntentSender.SendIntentException e) {
+                    sendError(remoteCallback, e);
+                }
+            } else if (Constants.ACTION_AWAIT_PACKAGE_REMOVED.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                awaitPackageBroadcast(
+                        remoteCallback, packageName, Intent.ACTION_PACKAGE_REMOVED, TIMEOUT_MS);
+            } else if (Constants.ACTION_AWAIT_PACKAGE_ADDED.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                awaitPackageBroadcast(
+                        remoteCallback, packageName, Intent.ACTION_PACKAGE_ADDED, TIMEOUT_MS);
+            } else if (Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                awaitPackageBroadcast(remoteCallback, packageName,
+                        Intent.ACTION_PACKAGE_FULLY_REMOVED, TIMEOUT_MS);
+            } else if (Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                awaitPackageBroadcast(remoteCallback, packageName,
+                        Intent.ACTION_PACKAGE_DATA_CLEARED, TIMEOUT_MS);
+            } else if (Constants.ACTION_QUERY_RESOLVER.equals(action)) {
+                final String authority = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                queryResolverForVisiblePackages(remoteCallback, authority);
+            } else if (Constants.ACTION_BIND_SERVICE.equals(action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                bindService(remoteCallback, packageName);
+            } else if (Constants.ACTION_GET_SYNCADAPTER_TYPES.equals(action)) {
+                sendSyncAdapterTypes(remoteCallback);
+            } else if (Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS.equals(action)) {
+                sendInstalledAppWidgetProviders(remoteCallback);
+            } else if (Constants.ACTION_AWAIT_PACKAGES_SUSPENDED.equals(action)) {
+                final String[] awaitPackages = intent.getBundleExtra(EXTRA_DATA)
+                        .getStringArray(EXTRA_PACKAGES);
+                awaitSuspendedPackagesBroadcast(remoteCallback, Arrays.asList(awaitPackages),
+                        Intent.ACTION_PACKAGES_SUSPENDED, TIMEOUT_MS);
+            } else if (Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED.equals(action)) {
+                final String componentName = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_COMPONENT_NAME);
+                sendIsActivityEnabled(remoteCallback, ComponentName.unflattenFromString(
+                        componentName));
+            } else if (Constants.ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS.equals(
+                    action)) {
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                sendGetSuspendedPackageLauncherExtras(remoteCallback, packageName);
+            } else if (Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY.equals(action)) {
+                final String authority = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_AUTHORITY);
+                final int userId = intent.getBundleExtra(EXTRA_DATA)
+                        .getInt(Intent.EXTRA_USER);
+                sendSyncAdapterPackagesForAuthorityAsUser(remoteCallback, authority, userId);
+            } else if (Constants.ACTION_REQUEST_SYNC_AND_AWAIT_STATUS.equals(action)) {
+                final String authority = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_AUTHORITY);
+                final Account account = intent.getBundleExtra(EXTRA_DATA)
+                        .getParcelable(EXTRA_ACCOUNT);
+                awaitRequestSyncStatus(remoteCallback, action, account, authority,
+                        EXTENDED_TIMEOUT_MS);
+            } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK.equals(action)) {
+                final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
+                        .getInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
+                awaitLauncherAppsCallback(remoteCallback, expectedEventCode, EXTENDED_TIMEOUT_MS);
+            } else if (Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES.equals(action)) {
+                final String sharedLibName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                sendGetSharedLibraryDependentPackages(remoteCallback, sharedLibName);
+            } else if (Constants.ACTION_GET_PREFERRED_ACTIVITIES.equals(action)) {
+                sendGetPreferredActivities(remoteCallback);
+            } else if (Constants.ACTION_SET_INSTALLER_PACKAGE_NAME.equals(action)) {
+                final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final String installerPackageName = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(Intent.EXTRA_INSTALLER_PACKAGE_NAME);
+                sendSetInstallerPackageName(remoteCallback, targetPackageName,
+                        installerPackageName);
+            } else if (Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES.equals(
+                    action)) {
+                sendGetInstalledAccessibilityServicePackages(remoteCallback);
+            } else if (Constants.ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS.equals(action)) {
+                final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final int userId = intent.getBundleExtra(EXTRA_DATA).getInt(Intent.EXTRA_USER);
+                sendLauncherAppsShouldHideFromSuggestions(remoteCallback, targetPackageName,
+                        userId);
+            } else if (Constants.ACTION_CHECK_URI_PERMISSION.equals(action)) {
+                final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final int targetUid = intent.getIntExtra(EXTRA_UID, INVALID_UID);
+                final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_AUTHORITY);
+                sendCheckUriPermission(remoteCallback, sourceAuthority, targetPackageName,
+                        targetUid);
+            } else if (Constants.ACTION_TAKE_PERSISTABLE_URI_PERMISSION.equals(action)) {
+                final Uri uri = intent.getData();
+                final int modeFlags = intent.getFlags();
+                if (uri != null) {
+                    getContentResolver().takePersistableUriPermission(uri, modeFlags);
+                }
+                finish();
+            } else if (Constants.ACTION_CAN_PACKAGE_QUERY.equals(action)) {
+                final String sourcePackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final String targetPackageName = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(Intent.EXTRA_PACKAGE_NAME);
+                sendCanPackageQuery(remoteCallback, sourcePackageName, targetPackageName);
+            } else if (Constants.ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS.equals(action)) {
+                final List<SessionInfo> infos = getSystemService(LauncherApps.class)
+                        .getAllPackageInstallerSessions();
+                sendSessionInfosListResult(remoteCallback, infos);
+            } else if (Constants.ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK.equals(action)) {
+                final int expectedEventCode = intent.getBundleExtra(EXTRA_DATA)
+                        .getInt(EXTRA_ID, SessionInfo.INVALID_ID);
+                awaitLauncherAppsSessionCallback(remoteCallback, expectedEventCode,
+                        EXTENDED_TIMEOUT_MS);
+            } else if (Constants.ACTION_GET_SESSION_INFO.equals(action)) {
+                final int sessionId = intent.getBundleExtra(EXTRA_DATA)
+                        .getInt(EXTRA_ID, SessionInfo.INVALID_ID);
+                final List<SessionInfo> infos = Arrays.asList(getPackageManager()
+                        .getPackageInstaller()
+                        .getSessionInfo(sessionId));
+                sendSessionInfosListResult(remoteCallback, infos);
+            } else if (Constants.ACTION_GET_STAGED_SESSIONS.equals(action)) {
+                final List<SessionInfo> infos = getPackageManager().getPackageInstaller()
+                        .getStagedSessions();
+                sendSessionInfosListResult(remoteCallback, infos);
+            } else if (Constants.ACTION_GET_ALL_SESSIONS.equals(action)) {
+                final List<SessionInfo> infos = getPackageManager().getPackageInstaller()
+                        .getAllSessions();
+                sendSessionInfosListResult(remoteCallback, infos);
+            } else if (Constants.ACTION_PENDING_INTENT_GET_ACTIVITY.equals(action)) {
+                sendPendingIntentGetActivity(remoteCallback);
+            } else if (Constants.ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE.equals(action)) {
+                sendPendingIntentGetCreatorPackage(remoteCallback,
+                        intent.getParcelableExtra(EXTRA_PENDING_INTENT));
+            } else if (Constants.ACTION_CHECK_PACKAGE.equals(action)) {
+                // Using ROOT_UID as default value here to pass the check in #verifyAndGetBypass,
+                // this is intended by design.
+                final int uid = intent.getIntExtra(EXTRA_UID, ROOT_UID);
+                final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                sendCheckPackageResult(remoteCallback, packageName, uid);
+            } else if (Constants.ACTION_GRANT_URI_PERMISSION.equals(action)) {
+                final String targetPackageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+                final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_AUTHORITY);
+                sendGrantUriPermission(remoteCallback, sourceAuthority, targetPackageName);
+            } else if (Constants.ACTION_REVOKE_URI_PERMISSION.equals(action)) {
+                final String sourceAuthority = intent.getBundleExtra(EXTRA_DATA)
+                        .getString(EXTRA_AUTHORITY);
+                sendRevokeUriPermission(remoteCallback, sourceAuthority);
+            } else {
+                sendError(remoteCallback, new Exception("unknown action " + action));
+            }
+        } catch (Exception e) {
+            sendError(remoteCallback, e);
+        }
+    }
+
+    private void sendGetInstalledAccessibilityServicePackages(RemoteCallback remoteCallback) {
+        final String[] packages = getSystemService(
+                AccessibilityManager.class).getInstalledAccessibilityServiceList().stream().map(
+                p -> p.getComponentName().getPackageName()).distinct().toArray(String[]::new);
+        final Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, packages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void onCommandReady(Intent intent) {
+        final RemoteCallback callback = intent.getParcelableExtra(EXTRA_REMOTE_READY_CALLBACK);
+        if (callback != null) {
+            callback.sendResult(null);
+        }
+    }
+
+    private void awaitPackageBroadcast(RemoteCallback remoteCallback, String packageName,
+            String action, long timeoutMs) {
+        final IntentFilter filter = new IntentFilter(action);
+        filter.addDataScheme("package");
+        filter.addDataSchemeSpecificPart(packageName, PatternMatcher.PATTERN_LITERAL);
+        final Object token = new Object();
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Bundle result = new Bundle();
+                result.putString(EXTRA_DATA, intent.getDataString());
+                remoteCallback.sendResult(result);
+                mainHandler.removeCallbacksAndMessages(token);
+                finish();
+            }
+        }, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
+        mainHandler.postDelayed(
+                () -> sendError(remoteCallback,
+                        new MissingBroadcastException(action, timeoutMs)),
+                token, timeoutMs);
+    }
+
+    private void awaitSuspendedPackagesBroadcast(RemoteCallback remoteCallback,
+            List<String> awaitList, String action, long timeoutMs) {
+        final IntentFilter filter = new IntentFilter(action);
+        final ArrayList<String> suspendedList = new ArrayList<>();
+        final Object token = new Object();
+        final Runnable sendResult = () -> {
+            final Bundle result = new Bundle();
+            result.putStringArray(EXTRA_PACKAGES, suspendedList.toArray(new String[] {}));
+            remoteCallback.sendResult(result);
+            finish();
+        };
+        registerReceiver(new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final Bundle extras = intent.getExtras();
+                final String[] changedList = extras.getStringArray(
+                        Intent.EXTRA_CHANGED_PACKAGE_LIST);
+                suspendedList.addAll(Arrays.stream(changedList).filter(
+                        p -> awaitList.contains(p)).collect(Collectors.toList()));
+                if (suspendedList.size() == awaitList.size()) {
+                    mainHandler.removeCallbacksAndMessages(token);
+                    sendResult.run();
+                }
+            }
+        }, filter, Context.RECEIVER_EXPORTED_UNAUDITED);
+        mainHandler.postDelayed(() -> sendResult.run(), token, timeoutMs);
+    }
+
+    private void awaitLauncherAppsCallback(RemoteCallback remoteCallback, int expectedEventCode,
+            long timeoutMs) {
+        final Object token = new Object();
+        final Bundle result = new Bundle();
+        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+        final LauncherApps.Callback launcherAppsCallback = new LauncherApps.Callback() {
+
+            private void onPackageStateUpdated(String[] packageNames, int resultCode) {
+                if (resultCode != expectedEventCode) {
+                    return;
+                }
+
+                mainHandler.removeCallbacksAndMessages(token);
+                result.putStringArray(EXTRA_PACKAGES, packageNames);
+                result.putInt(EXTRA_FLAGS, resultCode);
+                remoteCallback.sendResult(result);
+
+                launcherApps.unregisterCallback(this);
+                finish();
+            }
+
+            @Override
+            public void onPackageRemoved(String packageName, UserHandle user) {
+                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_REMOVED);
+            }
+
+            @Override
+            public void onPackageAdded(String packageName, UserHandle user) {
+                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_ADDED);
+            }
+
+            @Override
+            public void onPackageChanged(String packageName, UserHandle user) {
+                onPackageStateUpdated(new String[]{packageName}, CALLBACK_EVENT_PACKAGE_CHANGED);
+            }
+
+            @Override
+            public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                    boolean replacing) {
+                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_AVAILABLE);
+            }
+
+            @Override
+            public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                    boolean replacing) {
+                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNAVAILABLE);
+            }
+
+            @Override
+            public void onPackagesSuspended(String[] packageNames, UserHandle user) {
+                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_SUSPENDED);
+                super.onPackagesSuspended(packageNames, user);
+            }
+
+            @Override
+            public void onPackagesUnsuspended(String[] packageNames, UserHandle user) {
+                onPackageStateUpdated(packageNames, CALLBACK_EVENT_PACKAGES_UNSUSPENDED);
+                super.onPackagesUnsuspended(packageNames, user);
+            }
+        };
+
+        launcherApps.registerCallback(launcherAppsCallback);
+
+        mainHandler.postDelayed(() -> {
+            result.putStringArray(EXTRA_PACKAGES, new String[]{});
+            result.putInt(EXTRA_FLAGS, CALLBACK_EVENT_INVALID);
+            remoteCallback.sendResult(result);
+
+            launcherApps.unregisterCallback(launcherAppsCallback);
+            finish();
+        }, token, timeoutMs);
+    }
+
+    private void sendGetInstalledPackages(RemoteCallback remoteCallback, int flags) {
+        String[] packages =
+                getPackageManager().getInstalledPackages(flags)
+                        .stream().map(p -> p.packageName).distinct().toArray(String[]::new);
+        Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, packages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendQueryIntentActivities(RemoteCallback remoteCallback, Intent queryIntent) {
+        final String[] resolveInfos = getPackageManager().queryIntentActivities(
+                queryIntent, 0 /* flags */).stream()
+                .map(ri -> ri.activityInfo.applicationInfo.packageName)
+                .distinct()
+                .toArray(String[]::new);
+        Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendQueryIntentServices(RemoteCallback remoteCallback, Intent queryIntent) {
+        final String[] resolveInfos = getPackageManager().queryIntentServices(
+                queryIntent, 0 /* flags */).stream()
+                .map(ri -> ri.serviceInfo.applicationInfo.packageName)
+                .distinct()
+                .toArray(String[]::new);
+        Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendQueryIntentProviders(RemoteCallback remoteCallback, Intent queryIntent) {
+        final String[] resolveInfos = getPackageManager().queryIntentContentProviders(
+                queryIntent, 0 /* flags */).stream()
+                .map(ri -> ri.providerInfo.applicationInfo.packageName)
+                .distinct()
+                .toArray(String[]::new);
+        Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, resolveInfos);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void queryResolverForVisiblePackages(RemoteCallback remoteCallback, String authority) {
+        backgroundHandler.post(() -> {
+            Uri queryUri = Uri.parse("content://" + authority + "/test");
+            Cursor query = getContentResolver().query(queryUri, null, null, null, null);
+            if (query == null || !query.moveToFirst()) {
+                sendError(remoteCallback,
+                        new IllegalStateException(
+                                "Query of " + queryUri + " could not be completed"));
+                return;
+            }
+            ArrayList<String> visiblePackages = new ArrayList<>();
+            while (!query.isAfterLast()) {
+                visiblePackages.add(query.getString(0));
+                query.moveToNext();
+            }
+            query.close();
+
+            mainHandler.post(() -> {
+                Bundle result = new Bundle();
+                result.putStringArray(EXTRA_RETURN_RESULT, visiblePackages.toArray(new String[]{}));
+                remoteCallback.sendResult(result);
+                finish();
+            });
+
+        });
+    }
+
+    private void sendError(RemoteCallback remoteCallback, Exception failure) {
+        Bundle result = new Bundle();
+        result.putSerializable(EXTRA_ERROR, failure);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendPackageInfo(RemoteCallback remoteCallback, String packageName) {
+        final PackageInfo pi;
+        try {
+            pi = getPackageManager().getPackageInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            sendError(remoteCallback, e);
+            return;
+        }
+        Bundle result = new Bundle();
+        result.putParcelable(EXTRA_RETURN_RESULT, pi);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendPackagesForUid(RemoteCallback remoteCallback, int uid) {
+        final String[] packages = getPackageManager().getPackagesForUid(uid);
+        final Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, packages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendNameForUid(RemoteCallback remoteCallback, int uid) {
+        final String name = getPackageManager().getNameForUid(uid);
+        final Bundle result = new Bundle();
+        result.putString(EXTRA_RETURN_RESULT, name);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendNamesForUids(RemoteCallback remoteCallback, int uid) {
+        final String[] names = getPackageManager().getNamesForUids(new int[]{uid});
+        final Bundle result = new Bundle();
+        result.putStringArray(EXTRA_RETURN_RESULT, names);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendCheckSignatures(RemoteCallback remoteCallback, int uid1, int uid2) {
+        final int signatureResult = getPackageManager().checkSignatures(uid1, uid2);
+        final Bundle result = new Bundle();
+        result.putInt(EXTRA_RETURN_RESULT, signatureResult);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendHasSigningCertificate(RemoteCallback remoteCallback, int uid, byte[] cert,
+            int type) {
+        final boolean signatureResult = getPackageManager().hasSigningCertificate(uid, cert, type);
+        final Bundle result = new Bundle();
+        result.putBoolean(EXTRA_RETURN_RESULT, signatureResult);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    /**
+     * Instead of sending a list of package names, this function sends a List of
+     * {@link SyncAdapterType}, since the {@link SyncAdapterType#getPackageName()} is a test api
+     * which can only be invoked in the instrumentation.
+     */
+    private void sendSyncAdapterTypes(RemoteCallback remoteCallback) {
+        final SyncAdapterType[] types = ContentResolver.getSyncAdapterTypes();
+        final ArrayList<Parcelable> parcelables = new ArrayList<>();
+        for (SyncAdapterType type : types) {
+            parcelables.add(type);
+        }
+        final Bundle result = new Bundle();
+        result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendIsActivityEnabled(RemoteCallback remoteCallback, ComponentName componentName) {
+        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+        final Bundle result = new Bundle();
+        try {
+            result.putBoolean(EXTRA_RETURN_RESULT, launcherApps.isActivityEnabled(componentName,
+                    Process.myUserHandle()));
+        } catch (IllegalArgumentException e) {
+        }
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendGetSuspendedPackageLauncherExtras(RemoteCallback remoteCallback,
+            String packageName) {
+        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+        final Bundle result = new Bundle();
+        try {
+            result.putBundle(EXTRA_RETURN_RESULT,
+                    launcherApps.getSuspendedPackageLauncherExtras(packageName,
+                            Process.myUserHandle()));
+        } catch (IllegalArgumentException e) {
+        }
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendInstalledAppWidgetProviders(RemoteCallback remoteCallback) {
+        final AppWidgetManager appWidgetManager = getSystemService(AppWidgetManager.class);
+        final List<AppWidgetProviderInfo> providers = appWidgetManager.getInstalledProviders();
+        final ArrayList<Parcelable> parcelables = new ArrayList<>();
+        for (AppWidgetProviderInfo info : providers) {
+            parcelables.add(info);
+        }
+        final Bundle result = new Bundle();
+        result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendSyncAdapterPackagesForAuthorityAsUser(RemoteCallback remoteCallback,
+            String authority, int userId) {
+        final String[] syncAdapterPackages = ContentResolver
+                .getSyncAdapterPackagesForAuthorityAsUser(authority, userId);
+        final Bundle result = new Bundle();
+        result.putStringArray(Intent.EXTRA_PACKAGES, syncAdapterPackages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void awaitRequestSyncStatus(RemoteCallback remoteCallback, String action,
+            Account account, String authority, long timeoutMs) {
+        ContentResolver.cancelSync(account, authority);
+        final Object token = new Object();
+        final SyncStatusObserver observer = which -> {
+            final Bundle result = new Bundle();
+            result.putBoolean(EXTRA_RETURN_RESULT, true);
+            remoteCallback.sendResult(result);
+            mainHandler.removeCallbacksAndMessages(token);
+            finish();
+        };
+        syncStatusHandle = ContentResolver.addStatusChangeListener(
+                ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
+                        | ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS
+                        | ContentResolver.SYNC_OBSERVER_TYPE_PENDING, observer);
+
+        ContentResolver.requestSync(account, authority, new Bundle());
+        mainHandler.postDelayed(
+                () -> sendError(remoteCallback, new MissingCallbackException(action, timeoutMs)),
+                token, timeoutMs);
+    }
+
+    private void sendGetSharedLibraryDependentPackages(RemoteCallback remoteCallback,
+            String sharedLibName) {
+        final List<SharedLibraryInfo> sharedLibraryInfos = getPackageManager()
+                .getSharedLibraries(0 /* flags */);
+        SharedLibraryInfo sharedLibraryInfo = sharedLibraryInfos.stream().filter(
+                info -> sharedLibName.equals(info.getName())).findAny().orElse(null);
+        final String[] dependentPackages = sharedLibraryInfo == null ? null
+                : sharedLibraryInfo.getDependentPackages().stream()
+                        .map(versionedPackage -> versionedPackage.getPackageName())
+                        .distinct().collect(Collectors.toList()).toArray(new String[]{});
+        final Bundle result = new Bundle();
+        result.putStringArray(Intent.EXTRA_PACKAGES, dependentPackages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendGetPreferredActivities(RemoteCallback remoteCallback) {
+        final List<IntentFilter> filters = new ArrayList<>();
+        final List<ComponentName> activities = new ArrayList<>();
+        getPackageManager().getPreferredActivities(filters, activities, null /* packageName*/);
+        final String[] packages = activities.stream()
+                .map(componentName -> componentName.getPackageName()).distinct()
+                .collect(Collectors.toList()).toArray(new String[]{});
+        final Bundle result = new Bundle();
+        result.putStringArray(Intent.EXTRA_PACKAGES, packages);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendSetInstallerPackageName(RemoteCallback remoteCallback,
+            String targetPackageName, String installerPackageName) {
+        try {
+            getPackageManager().setInstallerPackageName(targetPackageName, installerPackageName);
+            remoteCallback.sendResult(null);
+            finish();
+        } catch (Exception e) {
+            sendError(remoteCallback, e);
+        }
+    }
+
+    private void sendLauncherAppsShouldHideFromSuggestions(RemoteCallback remoteCallback,
+            String targetPackageName, int userId) {
+        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+        final boolean hideFromSuggestions = launcherApps.shouldHideFromSuggestions(
+                targetPackageName, UserHandle.of(userId));
+        final Bundle result = new Bundle();
+        result.putBoolean(EXTRA_RETURN_RESULT, hideFromSuggestions);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendCheckUriPermission(RemoteCallback remoteCallback, String sourceAuthority,
+            String targetPackageName, int targetUid) {
+        final Uri uri = Uri.parse("content://" + sourceAuthority);
+        grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        final int permissionResult = checkUriPermission(uri, 0 /* pid */, targetUid,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        final Bundle result = new Bundle();
+        result.putInt(EXTRA_RETURN_RESULT, permissionResult);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendGrantUriPermission(RemoteCallback remoteCallback, String sourceAuthority,
+            String targetPackageName) {
+        final Uri uri = Uri.parse("content://" + sourceAuthority);
+        grantUriPermission(targetPackageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        remoteCallback.sendResult(null);
+        finish();
+    }
+
+    private void sendRevokeUriPermission(RemoteCallback remoteCallback, String sourceAuthority) {
+        final Uri uri = Uri.parse("content://" + sourceAuthority);
+        revokeUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        remoteCallback.sendResult(null);
+        finish();
+    }
+
+    private void sendCanPackageQuery(RemoteCallback remoteCallback, String sourcePackageName,
+            String targetPackageName) {
+        try {
+            final boolean visibility = getPackageManager().canPackageQuery(sourcePackageName,
+                    targetPackageName);
+            final Bundle result = new Bundle();
+            result.putBoolean(EXTRA_RETURN_RESULT, visibility);
+            remoteCallback.sendResult(result);
+            finish();
+        } catch (PackageManager.NameNotFoundException e) {
+            sendError(remoteCallback, e);
+        }
+    }
+
+    private void sendSessionInfosListResult(RemoteCallback remoteCallback,
+            List<SessionInfo> infos) {
+        final ArrayList<Parcelable> parcelables = new ArrayList<>(infos);
+        for (SessionInfo info : infos) {
+            parcelables.add(info);
+        }
+        final Bundle result = new Bundle();
+        result.putParcelableArrayList(EXTRA_RETURN_RESULT, parcelables);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void awaitLauncherAppsSessionCallback(RemoteCallback remoteCallback,
+            int expectedSessionId, long timeoutMs) {
+        final Object token = new Object();
+        final Bundle result = new Bundle();
+        final LauncherApps launcherApps = getSystemService(LauncherApps.class);
+        final SessionCallback sessionCallback = new SessionCallback() {
+
+            @Override
+            public void onCreated(int sessionId) {
+                // No-op
+            }
+
+            @Override
+            public void onBadgingChanged(int sessionId) {
+                // No-op
+            }
+
+            @Override
+            public void onActiveChanged(int sessionId, boolean active) {
+                // No-op
+            }
+
+            @Override
+            public void onProgressChanged(int sessionId, float progress) {
+                // No-op
+            }
+
+            @Override
+            public void onFinished(int sessionId, boolean success) {
+                if (sessionId != expectedSessionId) {
+                    return;
+                }
+
+                mainHandler.removeCallbacksAndMessages(token);
+                result.putInt(EXTRA_ID, sessionId);
+                remoteCallback.sendResult(result);
+
+                launcherApps.unregisterPackageInstallerSessionCallback(this);
+                finish();
+            }
+        };
+
+        launcherApps.registerPackageInstallerSessionCallback(this.getMainExecutor(),
+                sessionCallback);
+
+        mainHandler.postDelayed(() -> {
+            result.putInt(EXTRA_ID, SessionInfo.INVALID_ID);
+            remoteCallback.sendResult(result);
+
+            launcherApps.unregisterPackageInstallerSessionCallback(sessionCallback);
+            finish();
+        }, token, timeoutMs);
+    }
+
+    private void sendPendingIntentGetActivity(RemoteCallback remoteCallback) {
+        final PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* requestCode */,
+                new Intent(this, TestActivity.class), PendingIntent.FLAG_IMMUTABLE);
+        final Bundle result = new Bundle();
+        result.putParcelable(EXTRA_PENDING_INTENT, pendingIntent);
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendPendingIntentGetCreatorPackage(RemoteCallback remoteCallback,
+            PendingIntent pendingIntent) {
+        final Bundle result = new Bundle();
+        result.putString(Intent.EXTRA_PACKAGE_NAME, pendingIntent.getCreatorPackage());
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void sendCheckPackageResult(RemoteCallback remoteCallback, String packageName,
+            int uid) {
+        try {
+            getSystemService(AppOpsManager.class).checkPackage(uid, packageName);
+            final Bundle result = new Bundle();
+            result.putBoolean(EXTRA_RETURN_RESULT, true);
+            remoteCallback.sendResult(result);
+            finish();
+        } catch (SecurityException e) {
+            sendError(remoteCallback, e);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        final RemoteCallback remoteCallback = callbacks.get(requestCode);
+        if (resultCode != RESULT_OK) {
+            Exception e = (Exception) data.getSerializableExtra(EXTRA_ERROR);
+            sendError(remoteCallback, e == null ? new Exception("Result was " + resultCode) : e);
+            return;
+        }
+        final Bundle result = new Bundle();
+        result.putParcelable(EXTRA_RETURN_RESULT, data.getParcelableExtra(EXTRA_RETURN_RESULT));
+        remoteCallback.sendResult(result);
+        finish();
+    }
+
+    private void bindService(RemoteCallback remoteCallback, String packageName) {
+        final String SERVICE_NAME = "android.appenumeration.testapp.DummyService";
+        final Intent intent = new Intent();
+        intent.setClassName(packageName, SERVICE_NAME);
+        final ServiceConnection serviceConnection = new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                // No-op
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName className) {
+                // No-op
+            }
+
+            @Override
+            public void onBindingDied(ComponentName name) {
+                // Remote service die
+                finish();
+            }
+
+            @Override
+            public void onNullBinding(ComponentName name) {
+                // Since the DummyService doesn't implement onBind, it returns null and
+                // onNullBinding would be called. Use postDelayed to keep this service
+                // connection alive for 3 seconds.
+                mainHandler.postDelayed(() -> {
+                    unbindService(this);
+                    finish();
+                }, TIMEOUT_MS);
+            }
+        };
+
+        final boolean bound = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
+        final Bundle result = new Bundle();
+        result.putBoolean(EXTRA_RETURN_RESULT, bound);
+        remoteCallback.sendResult(result);
+        // Don't invoke finish() right here if service is bound successfully to keep the service
+        // connection alive since the ServiceRecord would be remove from the ServiceMap once no
+        // client is binding the service.
+        if (!bound) finish();
+    }
+}
diff --git a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
index 0f1d10f..aab4138 100644
--- a/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
+++ b/tests/tests/appenumeration/src/android/appenumeration/cts/AppEnumerationTests.java
@@ -18,8 +18,14 @@
 
 import static android.Manifest.permission.SET_PREFERRED_APPLICATIONS;
 import static android.appenumeration.cts.Constants.ACTION_AWAIT_LAUNCHER_APPS_CALLBACK;
+import static android.appenumeration.cts.Constants.ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK;
 import static android.appenumeration.cts.Constants.ACTION_BIND_SERVICE;
+import static android.appenumeration.cts.Constants.ACTION_CAN_PACKAGE_QUERY;
+import static android.appenumeration.cts.Constants.ACTION_CHECK_PACKAGE;
 import static android.appenumeration.cts.Constants.ACTION_CHECK_SIGNATURES;
+import static android.appenumeration.cts.Constants.ACTION_CHECK_URI_PERMISSION;
+import static android.appenumeration.cts.Constants.ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS;
+import static android.appenumeration.cts.Constants.ACTION_GET_ALL_SESSIONS;
 import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_ACCESSIBILITYSERVICES_PACKAGES;
 import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_APPWIDGET_PROVIDERS;
 import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
@@ -28,22 +34,32 @@
 import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGES_FOR_UID;
 import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
 import static android.appenumeration.cts.Constants.ACTION_GET_PREFERRED_ACTIVITIES;
+import static android.appenumeration.cts.Constants.ACTION_GET_SESSION_INFO;
 import static android.appenumeration.cts.Constants.ACTION_GET_SHAREDLIBRARY_DEPENDENT_PACKAGES;
+import static android.appenumeration.cts.Constants.ACTION_GET_STAGED_SESSIONS;
 import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_PACKAGES_FOR_AUTHORITY;
 import static android.appenumeration.cts.Constants.ACTION_GET_SYNCADAPTER_TYPES;
+import static android.appenumeration.cts.Constants.ACTION_GRANT_URI_PERMISSION;
 import static android.appenumeration.cts.Constants.ACTION_HAS_SIGNING_CERTIFICATE;
 import static android.appenumeration.cts.Constants.ACTION_JUST_FINISH;
+import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS;
 import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_IS_ACTIVITY_ENABLED;
+import static android.appenumeration.cts.Constants.ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS;
 import static android.appenumeration.cts.Constants.ACTION_MANIFEST_ACTIVITY;
 import static android.appenumeration.cts.Constants.ACTION_MANIFEST_PROVIDER;
 import static android.appenumeration.cts.Constants.ACTION_MANIFEST_SERVICE;
+import static android.appenumeration.cts.Constants.ACTION_PENDING_INTENT_GET_ACTIVITY;
+import static android.appenumeration.cts.Constants.ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_RESOLVER;
 import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
+import static android.appenumeration.cts.Constants.ACTION_REQUEST_SYNC_AND_AWAIT_STATUS;
+import static android.appenumeration.cts.Constants.ACTION_REVOKE_URI_PERMISSION;
 import static android.appenumeration.cts.Constants.ACTION_SET_INSTALLER_PACKAGE_NAME;
 import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
 import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
+import static android.appenumeration.cts.Constants.ACTION_TAKE_PERSISTABLE_URI_PERMISSION;
 import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_DUMMY_ACTIVITY;
 import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_TEST;
 import static android.appenumeration.cts.Constants.CALLBACK_EVENT_INVALID;
@@ -52,11 +68,14 @@
 import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_ADDED;
 import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_CHANGED;
 import static android.appenumeration.cts.Constants.CALLBACK_EVENT_PACKAGE_REMOVED;
+import static android.appenumeration.cts.Constants.EXTRA_ACCOUNT;
 import static android.appenumeration.cts.Constants.EXTRA_AUTHORITY;
 import static android.appenumeration.cts.Constants.EXTRA_CERT;
 import static android.appenumeration.cts.Constants.EXTRA_DATA;
 import static android.appenumeration.cts.Constants.EXTRA_ERROR;
 import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
+import static android.appenumeration.cts.Constants.EXTRA_ID;
+import static android.appenumeration.cts.Constants.EXTRA_PENDING_INTENT;
 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
 import static android.appenumeration.cts.Constants.EXTRA_REMOTE_READY_CALLBACK;
 import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
@@ -64,7 +83,11 @@
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PROVIDER;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_Q;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERM_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI;
+import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_RECEIVES_URI;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER_APK;
@@ -72,6 +95,7 @@
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_USES_LIBRARY;
 import static android.appenumeration.cts.Constants.QUERIES_NOTHING_USES_OPTIONAL_LIBRARY;
 import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
+import static android.appenumeration.cts.Constants.QUERIES_PACKAGE_PROVIDER;
 import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_ACTION;
 import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_AUTH;
 import static android.appenumeration.cts.Constants.QUERIES_SERVICE_ACTION;
@@ -98,6 +122,7 @@
 import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE_NORMAL;
 import static android.appenumeration.cts.Constants.TARGET_NO_API;
 import static android.appenumeration.cts.Constants.TARGET_PREFERRED_ACTIVITY;
+import static android.appenumeration.cts.Constants.TARGET_PREFIX_WILDCARD_WEB;
 import static android.appenumeration.cts.Constants.TARGET_SHARE;
 import static android.appenumeration.cts.Constants.TARGET_SHARED_LIBRARY_PACKAGE;
 import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
@@ -107,11 +132,13 @@
 import static android.appenumeration.cts.Constants.TARGET_SYNCADAPTER_SHARED_USER;
 import static android.appenumeration.cts.Constants.TARGET_WEB;
 import static android.content.Intent.EXTRA_PACKAGES;
+import static android.content.Intent.EXTRA_UID;
 import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.content.pm.PackageManager.SIGNATURE_MATCH;
 import static android.content.pm.PackageManager.SIGNATURE_UNKNOWN_PACKAGE;
 import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
@@ -119,6 +146,7 @@
 import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
 import static org.hamcrest.Matchers.containsString;
 import static org.hamcrest.Matchers.emptyArray;
+import static org.hamcrest.Matchers.emptyOrNullString;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.greaterThan;
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
@@ -132,13 +160,20 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import android.Manifest;
+import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.app.PendingIntent;
 import android.appwidget.AppWidgetProviderInfo;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SyncAdapterType;
+import android.content.pm.LauncherApps;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageInstaller.SessionInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.content.res.Resources;
@@ -155,8 +190,12 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.compatibility.common.util.SystemUtil;
 import com.android.compatibility.common.util.AmUtils;
+import com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.install.lib.Install;
+import com.android.cts.install.lib.InstallUtils;
+import com.android.cts.install.lib.LocalIntentSender;
+import com.android.cts.install.lib.TestApp;
 
 import org.hamcrest.core.IsNull;
 import org.junit.AfterClass;
@@ -177,6 +216,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.UUID;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicReference;
@@ -189,9 +229,17 @@
     private static boolean sGlobalFeatureEnabled;
 
     private static PackageManager sPm;
+    private static AccountManager sAccountManager;
 
     // The shared library for getting dependent packages
     private static final String TEST_SHARED_LIB_NAME = "android.test.runner";
+    private static final String TEST_NONEXISTENT_PACKAGE_NAME_1 = "com.android.cts.nonexistent1";
+    private static final String TEST_NONEXISTENT_PACKAGE_NAME_2 = "com.android.cts.nonexistent2";
+
+    private static final Account ACCOUNT_SYNCADAPTER = new Account(
+            TARGET_SYNCADAPTER, "android.appenumeration.account.type");
+    private static final Account ACCOUNT_SYNCADAPTER_SHARED_USER = new Account(
+            TARGET_SYNCADAPTER_SHARED_USER, "android.appenumeration.shareduid.account.type");
 
     @Rule
     public TestName name = new TestName();
@@ -216,12 +264,24 @@
         sResponseHandler = new Handler(sResponseThread.getLooper());
 
         sPm = InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+        sAccountManager = AccountManager.get(
+                InstrumentationRegistry.getInstrumentation().getContext());
+
+        assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER,
+                null /* password */, null /* userdata */), is(true));
+        assertThat(sAccountManager.addAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER,
+                null /* password */, null /* userdata */), is(true));
     }
 
     @AfterClass
     public static void tearDown() {
         if (!sGlobalFeatureEnabled) return;
         sResponseThread.quit();
+
+        assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER),
+                is(true));
+        assertThat(sAccountManager.removeAccountExplicitly(ACCOUNT_SYNCADAPTER_SHARED_USER),
+                is(true));
     }
 
     @Test
@@ -312,6 +372,45 @@
         assertVisible(QUERIES_NOTHING_RECEIVES_PERM_URI, QUERIES_NOTHING_PERM);
     }
 
+    @Test
+    public void startActivityWithUriGrant_cannotSeeProviderAfterUpdated() throws Exception {
+        assertNotVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+        // send with uri grant flags; should be visible
+        startExplicitActivityWithIntent(QUERIES_NOTHING_PERM,
+                QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI,
+                new Intent(ACTION_JUST_FINISH)
+                        .setData(Uri.parse("content://" + QUERIES_NOTHING_PERM + "3/test"))
+                        .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION));
+        assertVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+        // update the package; shouldn't be visible
+        runShellCommand("pm install " + QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI_APK);
+        // Wait until the updating is done
+        AmUtils.waitForBroadcastIdle();
+        assertNotVisible(QUERIES_NOTHING_RECEIVES_NON_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+    }
+
+    @Test
+    public void startActivityWithPersistableUriGrant_canSeeProviderAfterUpdated() throws Exception {
+        assertNotVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+        // send with persistable uri grant flags; should be visible
+        startExplicitActivityWithIntent(QUERIES_NOTHING_PERM,
+                QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI,
+                new Intent(ACTION_TAKE_PERSISTABLE_URI_PERMISSION)
+                        .setData(Uri.parse("content://" + QUERIES_NOTHING_PERM + "3/test"))
+                        .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+        assertVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+
+        // update the package; should be still visible
+        runShellCommand("pm install " + QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI_APK);
+        // Wait until the updating is done
+        AmUtils.waitForBroadcastIdle();
+        assertVisible(QUERIES_NOTHING_RECEIVES_PERSISTABLE_URI, QUERIES_NOTHING_PERM);
+    }
+
     private void startExplicitActivityWithIntent(
             String sourcePackageName, String targetPackageName, Intent intent) throws Exception {
         sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -639,6 +738,18 @@
         assertVisible(QUERIES_WILDCARD_BROWSER, TARGET_BROWSER_WILDCARD);
     }
 
+    @Test
+    public void queriesWildcardWeb_canSeePrefixWildcardWeb() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_PREFIX_WILDCARD_WEB);
+        assertVisible(QUERIES_WILDCARD_BROWSABLE, TARGET_PREFIX_WILDCARD_WEB);
+        assertVisible(QUERIES_WILDCARD_WEB, TARGET_PREFIX_WILDCARD_WEB);
+    }
+
+    @Test
+    public void queriesWildcardBrowser_cannotseePrefixWildcardWeb() throws Exception {
+        assertNotVisible(QUERIES_NOTHING, TARGET_PREFIX_WILDCARD_WEB);
+        assertNotVisible(QUERIES_WILDCARD_BROWSER, TARGET_PREFIX_WILDCARD_WEB);
+    }
 
     @Test
     public void queriesWildcardEditor() throws Exception {
@@ -761,6 +872,66 @@
     }
 
     @Test
+    public void uninstallTarget_broadcastFullyRemoved_notVisibleDoesNotReceive() throws Exception {
+        ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+        final Result result = sendCommand(QUERIES_NOTHING, TARGET_STUB,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
+                Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED, /* waitForReady */ true);
+        runShellCommand("pm uninstall " + TARGET_STUB);
+        try {
+            result.await();
+            fail();
+        } catch (MissingBroadcastException e) {
+            // hooray
+        }
+    }
+
+    @Test
+    public void uninstallTarget_broadcastFullyRemoved_visibleReceives() throws Exception {
+        ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+        final Result result = sendCommand(QUERIES_NOTHING_PERM, TARGET_STUB,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
+                Constants.ACTION_AWAIT_PACKAGE_FULLY_REMOVED, /* waitForReady */ true);
+        runShellCommand("pm uninstall " + TARGET_STUB);
+        try {
+            Assert.assertEquals(TARGET_STUB,
+                    Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
+        } catch (MissingBroadcastException e) {
+            fail();
+        }
+    }
+
+    @Test
+    public void clearTargetData_broadcastDataCleared_notVisibleDoesNotReceive() throws Exception {
+        ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+        final Result result = sendCommand(QUERIES_NOTHING, TARGET_STUB,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
+                Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED, /* waitForReady */ true);
+        runShellCommand("pm clear --user cur " + TARGET_STUB);
+        try {
+            result.await();
+            fail();
+        } catch (MissingBroadcastException e) {
+            // hooray
+        }
+    }
+
+    @Test
+    public void clearTargetData_broadcastDataCleared_visibleReceives() throws Exception {
+        ensurePackageIsInstalled(TARGET_STUB, TARGET_STUB_APK);
+        final Result result = sendCommand(QUERIES_NOTHING_PERM, TARGET_STUB,
+                /* targetUid */ INVALID_UID, /* intentExtra */ null,
+                Constants.ACTION_AWAIT_PACKAGE_DATA_CLEARED, /* waitForReady */ true);
+        runShellCommand("pm clear --user cur " + TARGET_STUB);
+        try {
+            Assert.assertEquals(TARGET_STUB,
+                    Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
+        } catch (MissingBroadcastException e) {
+            fail();
+        }
+    }
+
+    @Test
     public void broadcastSuspended_visibleReceives() throws Exception {
         assertBroadcastSuspendedVisible(QUERIES_PACKAGE,
                 Arrays.asList(TARGET_NO_API, TARGET_SYNCADAPTER),
@@ -923,6 +1094,549 @@
     }
 
     @Test
+    public void launcherAppsSessionCallback_queriesNothing_cannotSeeSession() throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+                    QUERIES_NOTHING, sessionId);
+            commitSession(sessionId);
+            final Bundle response = result.await();
+            assertThat(response.getInt(EXTRA_ID), equalTo(SessionInfo.INVALID_ID));
+        } finally {
+            runShellCommand("pm uninstall " + TestApp.A);
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherAppsSessionCallback_queriesNothingHasPermission_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+                    QUERIES_NOTHING_PERM, sessionId);
+            commitSession(sessionId);
+            final Bundle response = result.await();
+            assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+        } finally {
+            runShellCommand("pm uninstall " + TestApp.A);
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherAppsSessionCallback_queriesPackage_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+                    QUERIES_PACKAGE, sessionId);
+            commitSession(sessionId);
+            final Bundle response = result.await();
+            assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+        } finally {
+            runShellCommand("pm uninstall " + TestApp.A);
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherAppsSessionCallback_queriesNothingTargetsQ_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Result result = sendCommandAndWaitForLauncherAppsSessionCallback(
+                    QUERIES_NOTHING_Q, sessionId);
+            commitSession(sessionId);
+            final Bundle response = result.await();
+            assertThat(response.getInt(EXTRA_ID), equalTo(sessionId));
+        } finally {
+            runShellCommand("pm uninstall " + TestApp.A);
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherAppsSessionCallback_sessionOwner_canSeeSession() throws Exception {
+        try {
+            adoptShellPermissions();
+            final CountDownLatch countDownLatch = new CountDownLatch(1);
+            final int expectedSessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Context context = InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext();
+            final LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
+            final PackageInstaller.SessionCallback
+                    sessionCallback = new PackageInstaller.SessionCallback() {
+
+                @Override
+                public void onCreated(int sessionId) {
+                    // No-op
+                }
+
+                @Override
+                public void onBadgingChanged(int sessionId) {
+                    // No-op
+                }
+
+                @Override
+                public void onActiveChanged(int sessionId, boolean active) {
+                    // No-op
+                }
+
+                @Override
+                public void onProgressChanged(int sessionId, float progress) {
+                    // No-op
+                }
+
+                @Override
+                public void onFinished(int sessionId, boolean success) {
+                    if (sessionId != expectedSessionId) {
+                        return;
+                    }
+
+                    launcherApps.unregisterPackageInstallerSessionCallback(this);
+                    countDownLatch.countDown();
+                }
+            };
+
+            launcherApps.registerPackageInstallerSessionCallback(context.getMainExecutor(),
+                    sessionCallback);
+
+            commitSession(expectedSessionId);
+            assertTrue(countDownLatch.await(5, TimeUnit.SECONDS));
+        } finally {
+            runShellCommand("pm uninstall " + TestApp.A);
+            dropShellPermissions();
+        }
+    }
+
+    private Result sendCommandAndWaitForLauncherAppsSessionCallback(String sourcePackageName,
+            int expectedSessionId) throws Exception {
+        final Bundle extra = new Bundle();
+        extra.putInt(EXTRA_ID, expectedSessionId);
+        final Result result = sendCommand(sourcePackageName, /* targetPackageName */ null,
+                /* targetUid */ INVALID_UID, extra, ACTION_AWAIT_LAUNCHER_APPS_SESSION_CALLBACK,
+                /* waitForReady */ true);
+        return result;
+    }
+
+    @Test
+    public void launcherApps_getAllPkgInstallerSessions_queriesNothing_cannotSeeSessions()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+                    QUERIES_NOTHING, SessionInfo.INVALID_ID);
+            assertSessionNotVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherApps_getAllPkgInstallerSessions_queriesNothingHasPermission_canSeeSessions()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+                    QUERIES_NOTHING_PERM, SessionInfo.INVALID_ID);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherApps_getAllPkgInstallerSessions_queriesPackage_canSeeSessions()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+                    QUERIES_PACKAGE, SessionInfo.INVALID_ID);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherApps_getAllPkgInstallerSessions_queriesNothingTargetsQ_canSeeSessions()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_PACKAGE_INSTALLER_SESSIONS,
+                    QUERIES_NOTHING_Q, SessionInfo.INVALID_ID);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void launcherApps_getAllPkgInstallerSessions_sessionOwner_canSeeSessions()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final LauncherApps launcherApps = InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext()
+                    .getSystemService(LauncherApps.class);
+            final Integer[] sessionIds = launcherApps.getAllPackageInstallerSessions().stream()
+                    .map(i -> i.getSessionId())
+                    .distinct()
+                    .toArray(Integer[]::new);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getSessionInfo_queriesNothing_cannotSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+                    QUERIES_NOTHING, sessionId);
+            assertSessionNotVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getSessionInfo_queriesNothingHasPermission_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+                    QUERIES_NOTHING_PERM, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getSessionInfo_queriesPackage_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+                    QUERIES_PACKAGE, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getSessionInfo_queriesNothingTargetsQ_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_SESSION_INFO,
+                    QUERIES_NOTHING_Q, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getSessionInfo_sessionOwner_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final PackageInstaller installer = InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext()
+                    .getPackageManager()
+                    .getPackageInstaller();
+            final SessionInfo info = installer.getSessionInfo(sessionId);
+            assertThat(info, IsNull.notNullValue());
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getStagedSessions_queriesNothing_cannotSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+                    QUERIES_NOTHING, sessionId);
+            assertSessionNotVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getStagedSessions_queriesNothingHasPermission_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+                    QUERIES_NOTHING_PERM, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getStagedSessions_queriesPackage_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+                    QUERIES_PACKAGE, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getStagedSessions_queriesNothingTargetsQ_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_STAGED_SESSIONS,
+                    QUERIES_NOTHING_Q, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getStagedSessions_sessionOwner_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A).setStaged()
+                    .createSession();
+            final PackageInstaller installer = InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext()
+                    .getPackageManager()
+                    .getPackageInstaller();
+            final Integer[] sessionIds = installer.getStagedSessions().stream()
+                    .map(i -> i.getSessionId())
+                    .distinct()
+                    .toArray(Integer[]::new);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getAllSessions_queriesNothing_cannotSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+                    QUERIES_NOTHING, sessionId);
+            assertSessionNotVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getAllSessions_queriesNothingHasPermission_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+                    QUERIES_NOTHING_PERM, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getAllSessions_queriesPackage_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+                    QUERIES_PACKAGE, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getAllSessions_queriesNothingTargetsQ_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final Integer[] sessionIds = getSessionInfos(ACTION_GET_ALL_SESSIONS,
+                    QUERIES_NOTHING_Q, sessionId);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    @Test
+    public void packageInstaller_getAllSessions_sessionOwner_canSeeSession()
+            throws Exception {
+        try {
+            adoptShellPermissions();
+            final int sessionId = Install.single(TestApp.A1).setPackageName(TestApp.A)
+                    .createSession();
+            final PackageInstaller installer = InstrumentationRegistry
+                    .getInstrumentation()
+                    .getContext()
+                    .getPackageManager()
+                    .getPackageInstaller();
+            final Integer[] sessionIds = installer.getAllSessions().stream()
+                    .map(i -> i.getSessionId())
+                    .distinct()
+                    .toArray(Integer[]::new);
+            assertSessionVisible(sessionIds, sessionId);
+        } finally {
+            cleanUpSessions();
+            dropShellPermissions();
+        }
+    }
+
+    private Integer[] getSessionInfos(String action, String sourcePackageName, int sessionId)
+            throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putInt(EXTRA_ID, sessionId);
+        final Bundle response = sendCommandBlocking(sourcePackageName, /* targetPackageName */ null,
+                extraData, action);
+        final List<Parcelable> parcelables = response.getParcelableArrayList(
+                Intent.EXTRA_RETURN_RESULT);
+        return parcelables.stream()
+                .map(i -> (i == null ? SessionInfo.INVALID_ID : ((SessionInfo) i).getSessionId()))
+                .distinct()
+                .toArray(Integer[]::new);
+    }
+
+    private void assertSessionVisible(Integer[] sessionIds, int sessionId) {
+        if (!sGlobalFeatureEnabled) {
+            return;
+        }
+        assertThat(sessionIds, hasItemInArray(sessionId));
+    }
+
+    private void assertSessionNotVisible(Integer[] sessionIds, int sessionId) {
+        if (!sGlobalFeatureEnabled) {
+            return;
+        }
+        assertThat(sessionIds, not(hasItemInArray(sessionId)));
+    }
+
+    private static void commitSession(int sessionId) throws Exception {
+        final PackageInstaller.Session session =
+                InstallUtils.openPackageInstallerSession(sessionId);
+        final LocalIntentSender sender = new LocalIntentSender();
+        session.commit(sender.getIntentSender());
+        InstallUtils.assertStatusSuccess(sender.getResult());
+        if (session.isStaged()) {
+            InstallUtils.waitForSessionReady(sessionId);
+        }
+    }
+
+    private void adoptShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.INSTALL_PACKAGES);
+    }
+
+    private void dropShellPermissions() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    private void cleanUpSessions() {
+        InstallUtils.getPackageInstaller().getMySessions().forEach(info -> {
+            try {
+                InstallUtils.getPackageInstaller().abandonSession(info.getSessionId());
+            } catch (Exception ignore) {
+            }
+        });
+    }
+
+    @Test
     public void queriesResolver_grantsVisibilityToProvider() throws Exception {
         assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
 
@@ -983,6 +1697,32 @@
     }
 
     @Test
+    public void queriesPackage_requestSync_canSeeSyncadapterTarget()
+            throws Exception {
+        assertThat(requestSyncAndAwaitStatus(QUERIES_PACKAGE,
+                        ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER),
+                is(true));
+    }
+
+    @Test
+    public void queriesNothingSharedUser_requestSync_canSeeSyncadapterSharedUserTarget()
+            throws Exception {
+        assertThat(requestSyncAndAwaitStatus(QUERIES_NOTHING_SHARED_USER,
+                        ACCOUNT_SYNCADAPTER_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER),
+                is(true));
+    }
+
+    @Test
+    public void queriesNothing_requestSync_cannotSeeSyncadapterTarget() {
+        assertThrows(MissingCallbackException.class,
+                () -> requestSyncAndAwaitStatus(QUERIES_NOTHING,
+                        ACCOUNT_SYNCADAPTER, TARGET_SYNCADAPTER));
+        assertThrows(MissingCallbackException.class,
+                () -> requestSyncAndAwaitStatus(QUERIES_NOTHING,
+                        ACCOUNT_SYNCADAPTER_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER));
+    }
+
+    @Test
     public void queriesNothingSharedUser_getSyncAdapterPackages_canSeeSyncadapterSharedUserTarget()
             throws Exception {
         assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SYNCADAPTER_SHARED_USER,
@@ -1010,6 +1750,64 @@
     }
 
     @Test
+    public void launcherAppsGetSuspendedPackageLauncherExtras_queriesNothingHasPerm_canGetExtras()
+            throws Exception {
+        try {
+            setPackagesSuspendedWithLauncherExtras(/* suspend */ true,
+                    Arrays.asList(TARGET_NO_API), /* extras */ true);
+            Assert.assertNotNull(launcherAppsGetSuspendedPackageLauncherExtras(QUERIES_NOTHING_PERM,
+                            TARGET_NO_API));
+        } finally {
+            setPackagesSuspended(/* suspend */ false, Arrays.asList(TARGET_NO_API));
+        }
+    }
+
+    @Test
+    public void launcherAppsGetSuspendedPackageLauncherExtras_queriesNothing_cannotGetExtras()
+            throws Exception {
+        try {
+            setPackagesSuspendedWithLauncherExtras(/* suspend */ true,
+                    Arrays.asList(TARGET_NO_API), /* extras */ true);
+            Assert.assertNull(launcherAppsGetSuspendedPackageLauncherExtras(QUERIES_NOTHING,
+                    TARGET_NO_API));
+        } finally {
+            setPackagesSuspended(/* suspend */ false, Arrays.asList(TARGET_NO_API));
+        }
+    }
+
+    @Test
+    public void launcherAppsShouldHideFromSuggestions_queriesPackage_canSeeNoApi()
+            throws Exception {
+        setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS);
+
+        try {
+            final boolean hideFromSuggestions = shouldHideFromSuggestions(
+                    QUERIES_PACKAGE, TARGET_NO_API);
+            assertThat(hideFromSuggestions, is(true));
+        } finally {
+            setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+                    PackageManager.RESTRICTION_NONE);
+        }
+    }
+
+    @Test
+    public void launcherAppsShouldHideFromSuggestions_queriesNothing_cannotSeeNoApi()
+            throws Exception {
+        setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+                PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS);
+
+        try {
+            final boolean hideFromSuggestions = shouldHideFromSuggestions(
+                    QUERIES_NOTHING, TARGET_NO_API);
+            assertThat(hideFromSuggestions, is(false));
+        } finally {
+            setDistractingPackageRestrictions(new String[]{TARGET_NO_API},
+                    PackageManager.RESTRICTION_NONE);
+        }
+    }
+
+    @Test
     public void queriesPackage_canSeeAppWidgetProviderTarget() throws Exception {
         assumeTrue(InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
                 .hasSystemFeature(PackageManager.FEATURE_APP_WIDGETS));
@@ -1097,6 +1895,130 @@
         assertThat(ex.getMessage(), containsString(TARGET_NO_API));
     }
 
+    @Test
+    public void queriesPackageHasProvider_checkUriPermission_canSeeNoApi() throws Exception {
+        final int permissionResult = checkUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_NO_API);
+        assertThat(permissionResult, is(PackageManager.PERMISSION_GRANTED));
+    }
+
+    @Test
+    public void queriesPackageHasProvider_checkUriPermission_cannotSeeFilters() throws Exception {
+        final int permissionResult = checkUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_FILTERS);
+        assertThat(permissionResult, is(PackageManager.PERMISSION_DENIED));
+    }
+
+    @Test
+    public void queriesPackageHasProvider_grantUriPermission_canSeeNoApi() throws Exception {
+        try {
+            grantUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_NO_API);
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext()
+                    .checkUriPermission(
+                            Uri.parse("content://" + QUERIES_PACKAGE_PROVIDER),
+                            0 /* pid */,
+                            sPm.getPackageUid(TARGET_NO_API, 0 /* flags */),
+                            Intent.FLAG_GRANT_READ_URI_PERMISSION),
+                    is(PackageManager.PERMISSION_GRANTED));
+        } finally {
+            revokeUriPermission(QUERIES_PACKAGE_PROVIDER);
+        }
+    }
+
+    @Test
+    public void queriesPackageHasProvider_grantUriPermission_cannotSeeFilters() throws Exception {
+        try {
+            grantUriPermission(QUERIES_PACKAGE_PROVIDER, TARGET_FILTERS);
+            assertThat(InstrumentationRegistry.getInstrumentation().getContext()
+                            .checkUriPermission(
+                                    Uri.parse("content://" + QUERIES_PACKAGE_PROVIDER),
+                                    0 /* pid */,
+                                    sPm.getPackageUid(TARGET_FILTERS, 0 /* flags */),
+                                    Intent.FLAG_GRANT_READ_URI_PERMISSION),
+                    is(PackageManager.PERMISSION_DENIED));
+        } finally {
+            revokeUriPermission(QUERIES_PACKAGE_PROVIDER);
+        }
+    }
+
+    @Test
+    public void canPackageQuery_queriesActivityAction_canSeeFilters() throws Exception {
+        assertThat(sPm.canPackageQuery(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS),
+                is(true));
+    }
+
+    @Test
+    public void canPackageQuery_queriesNothing_cannotSeeFilters() throws Exception {
+        assertThat(sPm.canPackageQuery(QUERIES_NOTHING, TARGET_FILTERS),
+                is(false));
+    }
+
+    @Test
+    public void canPackageQuery_withNonexistentPackages() {
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> sPm.canPackageQuery(
+                        TEST_NONEXISTENT_PACKAGE_NAME_1, TEST_NONEXISTENT_PACKAGE_NAME_2));
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> sPm.canPackageQuery(
+                        QUERIES_NOTHING_PERM, TEST_NONEXISTENT_PACKAGE_NAME_2));
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> sPm.canPackageQuery(
+                        TEST_NONEXISTENT_PACKAGE_NAME_1, TARGET_FILTERS));
+    }
+
+    @Test
+    public void canPackageQuery_callerHasNoPackageVisibility() {
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> canPackageQuery(
+                        QUERIES_NOTHING, QUERIES_ACTIVITY_ACTION, TARGET_FILTERS));
+        assertThrows(PackageManager.NameNotFoundException.class,
+                () -> canPackageQuery(
+                        QUERIES_NOTHING_SHARED_USER, QUERIES_PACKAGE, TARGET_SHARED_USER));
+    }
+
+    @Test
+    public void checkPackage_queriesNothing_validateFailed() {
+        // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+        assertThrows(SecurityException.class,
+                () -> checkPackage(QUERIES_NOTHING, TARGET_FILTERS, ROOT_UID));
+    }
+
+    @Test
+    public void checkPackage_queriesNothing_targetIsNotExisting_validateFailed() {
+        // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+        assertThrows(SecurityException.class,
+                () -> checkPackage(QUERIES_NOTHING, TEST_NONEXISTENT_PACKAGE_NAME_1, ROOT_UID));
+    }
+
+    @Test
+    public void checkPackage_queriesNothingHasPerm_validateSuccessful() throws Exception {
+        // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+        assertThat(checkPackage(QUERIES_NOTHING_PERM, TARGET_FILTERS, ROOT_UID), is(true));
+    }
+
+    @Test
+    public void checkPackage_queriesNothingHasPerm_targetIsNotExisting_validateFailed()
+            throws Exception {
+        // Using ROOT_UID here to pass the check in #verifyAndGetBypass, this is intended by design.
+        assertThrows(SecurityException.class,
+                () -> checkPackage(QUERIES_NOTHING_PERM, TEST_NONEXISTENT_PACKAGE_NAME_1,
+                        ROOT_UID));
+    }
+
+    @Test
+    public void pendingIntent_getCreatorPackage_queriesPackage_canSeeNoApi()
+            throws Exception {
+        final PendingIntent pendingIntent = getPendingIntentActivity(TARGET_NO_API);
+        assertThat(getPendingIntentCreatorPackage(QUERIES_PACKAGE, pendingIntent),
+                is(TARGET_NO_API));
+    }
+
+    @Test
+    public void pendingIntent_getCreatorPackage_queriesNothing_cannotSeeNoApi()
+            throws Exception {
+        final PendingIntent pendingIntent = getPendingIntentActivity(TARGET_NO_API);
+        assertThat(getPendingIntentCreatorPackage(QUERIES_NOTHING, pendingIntent),
+                is(emptyOrNullString()));
+    }
+
     private void assertNotVisible(String sourcePackageName, String targetPackageName)
             throws Exception {
         if (!sGlobalFeatureEnabled) return;
@@ -1270,6 +2192,15 @@
         return certs;
     }
 
+    private boolean checkPackage(String sourcePackageName, String targetPackageName, int targetUid)
+            throws Exception {
+        final Bundle extra = new Bundle();
+        extra.putInt(EXTRA_UID, targetUid);
+        final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName, extra,
+                ACTION_CHECK_PACKAGE);
+        return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+    }
+
     private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
             throws Exception {
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -1283,7 +2214,7 @@
                 InstrumentationRegistry.getInstrumentation().getContext(), 100,
                 new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
                         new ComponentName(targetPackageName,
-                                "android.appenumeration.cts.query.TestActivity")),
+                                "android.appenumeration.cts.TestActivity")),
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
 
         Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
@@ -1377,7 +2308,22 @@
         return response.getStringArray(Intent.EXTRA_PACKAGES);
     }
 
+    private boolean requestSyncAndAwaitStatus(String sourcePackageName, Account account,
+            String targetPackageName) throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putParcelable(EXTRA_ACCOUNT, account);
+        extraData.putString(EXTRA_AUTHORITY, targetPackageName + ".authority");
+        final Bundle response = sendCommandBlocking(sourcePackageName, /* targetPackageName */ null,
+                extraData, ACTION_REQUEST_SYNC_AND_AWAIT_STATUS);
+        return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+    }
+
     private void setPackagesSuspended(boolean suspend, List<String> packages) {
+        setPackagesSuspendedWithLauncherExtras(suspend, packages, /* persistableBundle */ false);
+    }
+
+    private void setPackagesSuspendedWithLauncherExtras(boolean suspend, List<String> packages,
+            boolean extras) {
         final StringBuilder cmd = new StringBuilder("pm ");
         if (suspend) {
             cmd.append("suspend");
@@ -1385,6 +2331,9 @@
             cmd.append("unsuspend");
         }
         cmd.append(" --user cur");
+        if (extras) {
+            cmd.append(" --les foo bar");
+        }
         packages.stream().forEach(p -> cmd.append(" ").append(p));
         runShellCommand(cmd.toString());
     }
@@ -1398,6 +2347,13 @@
         return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
     }
 
+    private Bundle launcherAppsGetSuspendedPackageLauncherExtras(String sourcePackageName,
+            String targetPackageName) throws Exception {
+        final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
+                /* extraData */ null, ACTION_LAUNCHER_APPS_GET_SUSPENDED_PACKAGE_LAUNCHER_EXTRAS);
+        return response.getBundle(Intent.EXTRA_RETURN_RESULT);
+    }
+
     private String[] getSharedLibraryDependentPackages(String sourcePackageName) throws Exception {
         final Bundle extraData = new Bundle();
         final Bundle response = sendCommandBlocking(sourcePackageName, TEST_SHARED_LIB_NAME,
@@ -1420,6 +2376,70 @@
                 extraData, ACTION_SET_INSTALLER_PACKAGE_NAME);
     }
 
+    private void setDistractingPackageRestrictions(String[] packagesToRestrict,
+            int distractionFlags) throws Exception {
+        final String[] failed = SystemUtil.callWithShellPermissionIdentity(
+                () -> sPm.setDistractingPackageRestrictions(packagesToRestrict, distractionFlags));
+        assertThat(failed, emptyArray());
+    }
+
+    private boolean shouldHideFromSuggestions(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putInt(Intent.EXTRA_USER, Process.myUserHandle().getIdentifier());
+        final Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName, extraData,
+                ACTION_LAUNCHER_APPS_SHOULD_HIDE_FROM_SUGGESTIONS);
+        return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+    }
+
+    private int checkUriPermission(String sourcePackageName, String targetPackageName)
+            throws Exception {
+        final int targetUid = sPm.getPackageUid(targetPackageName, /* flags */ 0);
+        final Bundle extraData = new Bundle();
+        extraData.putString(EXTRA_AUTHORITY, sourcePackageName);
+        final Result result = sendCommand(sourcePackageName, targetPackageName, targetUid,
+                extraData, ACTION_CHECK_URI_PERMISSION, /* waitForReady */ false);
+        final Bundle response = result.await();
+        return response.getInt(Intent.EXTRA_RETURN_RESULT);
+    }
+
+    private void grantUriPermission(String providerPackageName, String targetPackageName)
+            throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putString(EXTRA_AUTHORITY, providerPackageName);
+        sendCommandBlocking(providerPackageName, targetPackageName, extraData,
+                ACTION_GRANT_URI_PERMISSION);
+    }
+
+    private void revokeUriPermission(String providerPackageName) throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putString(EXTRA_AUTHORITY, providerPackageName);
+        sendCommandBlocking(providerPackageName, null /* targetPackageName */, extraData,
+                ACTION_REVOKE_URI_PERMISSION);
+    }
+
+    private boolean canPackageQuery(String callerPackageName, String sourcePackageName,
+            String targetPackageName) throws Exception {
+        final Bundle extraData = new Bundle();
+        extraData.putString(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
+        final Bundle response = sendCommandBlocking(callerPackageName, sourcePackageName,
+                extraData, ACTION_CAN_PACKAGE_QUERY);
+        return response.getBoolean(Intent.EXTRA_RETURN_RESULT);
+    }
+
+    private PendingIntent getPendingIntentActivity(String sourcePackageName) throws Exception  {
+        final Bundle bundle = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
+                null /* intentExtra */, ACTION_PENDING_INTENT_GET_ACTIVITY);
+        return bundle.getParcelable(EXTRA_PENDING_INTENT);
+    }
+
+    private String getPendingIntentCreatorPackage(String sourcePackageName,
+            PendingIntent pendingIntent) throws Exception  {
+        final Bundle bundle = sendCommandBlocking(sourcePackageName, null /* targetPackageName */,
+                pendingIntent, ACTION_PENDING_INTENT_GET_CREATOR_PACKAGE);
+        return bundle.getString(Intent.EXTRA_PACKAGE_NAME);
+    }
+
     interface Result {
         Bundle await() throws Exception;
     }
@@ -1443,7 +2463,7 @@
             if (intentExtra instanceof Intent) {
                 intent.putExtra(Intent.EXTRA_INTENT, intentExtra);
             } else if (intentExtra instanceof PendingIntent) {
-                intent.putExtra("pendingIntent", intentExtra);
+                intent.putExtra(EXTRA_PENDING_INTENT, intentExtra);
             } else if (intentExtra instanceof Bundle) {
                 intent.putExtra(EXTRA_DATA, intentExtra);
             }
diff --git a/tests/tests/appop/AndroidTest.xml b/tests/tests/appop/AndroidTest.xml
index f817422..b25d2b8 100644
--- a/tests/tests/appop/AndroidTest.xml
+++ b/tests/tests/appop/AndroidTest.xml
@@ -17,6 +17,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
diff --git a/tests/tests/appop2/AndroidTest.xml b/tests/tests/appop2/AndroidTest.xml
index fc10a83..aa3b23f 100644
--- a/tests/tests/appop2/AndroidTest.xml
+++ b/tests/tests/appop2/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
index 20410e7..6d96eca 100644
--- a/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/deviceidle/DeviceIdleTest.java
@@ -15,23 +15,48 @@
  */
 package android.os.cts.deviceidle;
 
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
+import android.annotation.NonNull;
 import android.content.Context;
+import android.content.IntentFilter;
 import android.os.PowerManager;
+import android.support.test.uiautomator.UiDevice;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.BatteryUtils;
+import com.android.compatibility.common.util.CallbackAsserter;
+
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class DeviceIdleTest {
+
+    private UiDevice mUiDevice;
+
+    @Before
+    public void setUp() {
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        BatteryUtils.runDumpsysBatteryReset();
+    }
+
     @Test
     public void testDeviceIdleManager() {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -39,6 +64,62 @@
     }
 
     @Test
+    public void testLightIdleMode() throws Exception {
+        final String output = mUiDevice.executeShellCommand("cmd deviceidle enabled light").trim();
+        final boolean isEnabled = Integer.parseInt(output) != 0;
+        assumeTrue("device idle not enabled", isEnabled);
+
+        // Reset idle state.
+        setScreenState(true);
+        BatteryUtils.runDumpsysBatteryUnplug();
+        setScreenState(false);
+        PowerManager powerManager = InstrumentationRegistry.getInstrumentation().getContext()
+                .getSystemService(PowerManager.class);
+        waitUntil("Never made it to IDLE state", 30 /* seconds */, () -> {
+            final CallbackAsserter idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                    new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+            stepIdleState("light");
+            final String state = getIdleState("light");
+            if ("IDLE".equals(state)) {
+                idleModeChangedBroadcastAsserter.assertCalled(
+                        "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+                assertTrue(powerManager.isDeviceLightIdleMode());
+                return true;
+            } else {
+                assertFalse("Returned true even though state is " + state,
+                        powerManager.isDeviceLightIdleMode());
+                return false;
+            }
+        });
+
+        // We're IDLE. Step to IDLE_MAINTENANCE and confirm false is returned.
+        CallbackAsserter idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        stepIdleState("light");
+        idleModeChangedBroadcastAsserter.assertCalled(
+                "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+        String state = getIdleState("light");
+        assertEquals("IDLE_MAINTENANCE", state);
+        assertFalse(powerManager.isDeviceLightIdleMode());
+
+        idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        stepIdleState("light");
+        idleModeChangedBroadcastAsserter.assertCalled(
+                "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+        state = getIdleState("light");
+        assertEquals("IDLE", state);
+        assertTrue(powerManager.isDeviceLightIdleMode());
+
+        idleModeChangedBroadcastAsserter = CallbackAsserter.forBroadcast(
+                new IntentFilter(PowerManager.ACTION_DEVICE_LIGHT_IDLE_MODE_CHANGED));
+        setScreenState(true);
+        idleModeChangedBroadcastAsserter.assertCalled(
+                "Didn't get light idle mode changed broadcast", 15 /* 15 seconds */);
+        assertFalse(powerManager.isDeviceLightIdleMode());
+    }
+
+    @Test
     public void testPowerManagerIgnoringBatteryOptimizations() {
         Context context = InstrumentationRegistry.getInstrumentation().getContext();
 
@@ -48,4 +129,26 @@
                 .isIgnoringBatteryOptimizations("no.such.package.!!!"));
     }
 
+    @NonNull
+    private String getIdleState(String level) throws Exception {
+        return mUiDevice.executeShellCommand("cmd deviceidle get " + level).trim();
+    }
+
+    private void stepIdleState(String level) throws Exception {
+        mUiDevice.executeShellCommand("cmd deviceidle step " + level);
+    }
+
+    /**
+     * Set the screen state.
+     */
+    private void setScreenState(boolean on) throws Exception {
+        if (on) {
+            mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+            mUiDevice.executeShellCommand("wm dismiss-keyguard");
+        } else {
+            mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
+        }
+        // Wait a little bit to make sure the screen state has changed.
+        Thread.sleep(2_000);
+    }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
index 43183fe..d30e334 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
@@ -259,6 +259,18 @@
         socket.close();
     }
 
+    public void test_discoverableTimeout() {
+        if (!mHasBluetooth) {
+            // Skip the test if bluetooth is not present.
+            return;
+        }
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        assertTrue(BTAdapterUtils.disableAdapter(adapter, mContext));
+        assertEquals(null, adapter.getDiscoverableTimeout());
+        assertTrue(BTAdapterUtils.enableAdapter(adapter, mContext));
+        assertEquals(120, adapter.getDiscoverableTimeout().toSeconds());
+    }
+
     private static void sleep(long t) {
         try {
             Thread.sleep(t);
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
index 3d407df..42b1ecc 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothClassTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -34,7 +34,8 @@
 
     @Override
     protected void setUp() {
-        mBluetoothClassHeadphones = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        mBluetoothClassHeadphones =
+                new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
         mBluetoothClassPhone = new BluetoothClass(BluetoothClass.Device.Major.PHONE);
         mBluetoothClassService = new BluetoothClass(BluetoothClass.Service.NETWORKING);
     }
@@ -47,19 +48,27 @@
 
     @SmallTest
     public void testGetMajorDeviceClass() {
-        assertEquals(mBluetoothClassHeadphones.getMajorDeviceClass(), BluetoothClass.Device.Major.AUDIO_VIDEO);
+        assertEquals(
+                mBluetoothClassHeadphones.getMajorDeviceClass(),
+                BluetoothClass.Device.Major.AUDIO_VIDEO);
         assertEquals(mBluetoothClassPhone.getMajorDeviceClass(), BluetoothClass.Device.Major.PHONE);
     }
 
     @SmallTest
     public void testGetDeviceClass() {
-        assertEquals(mBluetoothClassHeadphones.getDeviceClass(), BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
-        assertEquals(mBluetoothClassPhone.getDeviceClass(), BluetoothClass.Device.PHONE_UNCATEGORIZED);
+        assertEquals(
+                mBluetoothClassHeadphones.getDeviceClass(),
+                BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        assertEquals(
+                mBluetoothClassPhone.getDeviceClass(),
+                BluetoothClass.Device.PHONE_UNCATEGORIZED);
     }
 
     @SmallTest
     public void testGetClassOfDevice() {
-        assertEquals(mBluetoothClassHeadphones.getClassOfDevice(), BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
+        assertEquals(
+                mBluetoothClassHeadphones.getClassOfDevice(),
+                BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES);
         assertEquals(mBluetoothClassPhone.getClassOfDevice(), BluetoothClass.Device.Major.PHONE);
     }
 
@@ -74,4 +83,13 @@
         assertTrue(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_PANU));
         assertFalse(mBluetoothClassService.doesClassMatch(BluetoothClass.PROFILE_OPP));
     }
+
+    @SmallTest
+    public void testInnerClasses() {
+        // Just instantiate static inner classes for exposing constants
+        // to make test coverage tool happy.
+        BluetoothClass.Device device = new BluetoothClass.Device();
+        BluetoothClass.Device.Major major = new BluetoothClass.Device.Major();
+        BluetoothClass.Service service = new BluetoothClass.Service();
+    }
 }
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
index 7f1a487..f5a99ce 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -85,7 +85,8 @@
                 "cmd companiondevice associate %d %s %s", userId, packageName, deviceAddress));
         String output = runShellCommand("dumpsys companiondevice");
         assertTrue("Package name missing from output", output.contains(packageName));
-        assertTrue("Device address missing from output", output.contains(deviceAddress));
+        assertTrue("Device address missing from output",
+                output.toLowerCase().contains(deviceAddress.toLowerCase()));
 
         // Takes time to update the CDM cache, so sleep to ensure the association is cached
         try {
diff --git a/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java b/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
index 54c68e7..23c549f 100644
--- a/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
+++ b/tests/tests/car/PermissionReadCarDisplayUnits/src/android/car/cts/permissionreadcardisplayunits/PermissionReadCarDisplayUnitsTest.java
@@ -45,7 +45,7 @@
                     VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
                     VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
                     VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
-                    /*VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS=*/ 289408516,
+                    VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
                     VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME)
                     .build();
 
@@ -69,4 +69,4 @@
                     PERMISSION_READ_CAR_DISPLAY_UNITS_PROPERTIES);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/car/src/android/car/cts/CarApiTestBase.java b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
index 771978c..7cb3c89 100644
--- a/tests/tests/car/src/android/car/cts/CarApiTestBase.java
+++ b/tests/tests/car/src/android/car/cts/CarApiTestBase.java
@@ -78,6 +78,10 @@
         }
     }
 
+    public void startUser(int userId) throws Exception {
+        executeShellCommand("am start-user %d", userId);
+    }
+
     protected synchronized Car getCar() {
         return mCar;
     }
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index ac17aac..7276419 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -29,6 +29,7 @@
 import android.car.VehicleAreaType;
 import android.car.VehicleAreaWheel;
 import android.car.VehicleGear;
+import android.car.VehicleIgnitionState;
 import android.car.VehiclePropertyIds;
 import android.car.cts.utils.VehiclePropertyVerifier;
 import android.car.hardware.CarPropertyConfig;
@@ -625,13 +626,10 @@
                     assertWithMessage(
                             "IGNITION_STATE must be a defined ignition state: "
                                     + ignitionState).that(
-                            ignitionState).isIn(ImmutableSet.of(
-                            /*VehicleIgnitionState.UNDEFINED=*/0,
-                            /*VehicleIgnitionState.LOCK=*/1,
-                            /*VehicleIgnitionState.OFF=*/2,
-                            /*VehicleIgnitionState.ACC=*/3,
-                            /*VehicleIgnitionState.ON=*/4,
-                            /*VehicleIgnitionState.START=*/5));
+                            ignitionState).isIn(ImmutableSet.of(VehicleIgnitionState.UNDEFINED,
+                            VehicleIgnitionState.LOCK, VehicleIgnitionState.OFF,
+                            VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
+                            VehicleIgnitionState.START));
                 }).build().verify(mCarPropertyManager);
     }
 
@@ -681,8 +679,7 @@
 
     @Test
     public void testVehicleSpeedDisplayUnitsIfSupported() {
-        VehiclePropertyVerifier.newBuilder(/*VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS=*/
-                289408516,
+        VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
                 CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
                 VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
                 CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
diff --git a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
new file mode 100644
index 0000000..3faf7c9
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static org.testng.Assert.fail;
+
+import android.car.Car;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.car.user.CarUserManager.UserLifecycleListener;
+import android.os.NewUserRequest;
+import android.os.NewUserResponse;
+import android.os.SystemClock;
+import android.os.UserManager;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class CarServiceHelperServiceUpdatableTest extends CarApiTestBase {
+
+    private static final String TAG = CarServiceHelperServiceUpdatableTest.class.getSimpleName();
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Test
+    public void testSendUserLifecycleEvent() throws Exception {
+        // Add listener to check if user started
+        CarUserManager carUserManager = (CarUserManager) getCar()
+                .getCarManager(Car.CAR_USER_SERVICE);
+        LifecycleListener listener = new LifecycleListener();
+        carUserManager.addListener(Runnable::run, listener);
+
+        NewUserResponse response = null;
+        UserManager userManager = null;
+        try {
+            // get create User permissions
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .adoptShellPermissionIdentity(android.Manifest.permission.CREATE_USERS);
+
+            // CreateUser
+            userManager = mContext.getSystemService(UserManager.class);
+            response = userManager.createUser(new NewUserRequest.Builder().build());
+            assertThat(response.isSuccessful()).isTrue();
+
+            int userId = response.getUser().getIdentifier();
+            startUser(userId);
+            listener.assertEventReceived(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING,
+                    /* timeoutMs= */ 60_000);
+        } finally {
+            userManager.removeUser(response.getUser());
+            carUserManager.removeListener(listener);
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+
+    // TODO(214100537): Improve listener by removing sleep.
+    private final class LifecycleListener implements UserLifecycleListener {
+
+        private final List<UserLifecycleEvent> mEvents =
+                new ArrayList<CarUserManager.UserLifecycleEvent>();
+
+        private final Object mLock = new Object();
+
+        @Override
+        public void onEvent(UserLifecycleEvent event) {
+            Log.d(TAG, "Event received: " + event);
+            synchronized (mLock) {
+                mEvents.add(event);
+            }
+        }
+
+        public void assertEventReceived(int userId, int eventType, int timeoutMs)
+                throws InterruptedException {
+            long startTime = SystemClock.elapsedRealtime();
+            while (SystemClock.elapsedRealtime() - startTime < timeoutMs) {
+                boolean result = checkEvent(userId, eventType);
+                if (result) return;
+                Thread.sleep(1000);
+            }
+
+            fail("Event" + eventType + " was not received within timeoutMs: " + timeoutMs);
+        }
+
+        private boolean checkEvent(int userId, int eventType) {
+            synchronized (mLock) {
+                for (int i = 0; i < mEvents.size(); i++) {
+                    if (mEvents.get(i).getUserHandle().getIdentifier() == userId
+                            && mEvents.get(i).getEventType() == eventType) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarTest.java b/tests/tests/car/src/android/car/cts/CarTest.java
index 8527189..3c9fff5 100644
--- a/tests/tests/car/src/android/car/cts/CarTest.java
+++ b/tests/tests/car/src/android/car/cts/CarTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.IBinder;
 import android.platform.test.annotations.AppModeFull;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -118,6 +119,31 @@
         listenerImpl.waitForReady(DEFAULT_WAIT_TIMEOUT_MS);
     }
 
+    @Test
+    public void testApiVersion() throws Exception {
+        int ApiVersionTooHigh = 1000000;
+        int MinorApiVersionTooHigh = 1000000;
+        assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT)).isTrue();
+        assertThat(Car.isApiVersionAtLeast(ApiVersionTooHigh)).isFalse();
+
+        assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT -1 ,
+                MinorApiVersionTooHigh)).isTrue();
+        assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                Car.API_VERSION_MINOR_INT)).isTrue();
+        assertThat(Car.isApiVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                MinorApiVersionTooHigh)).isFalse();
+        assertThat(Car.isApiVersionAtLeast(ApiVersionTooHigh, 0)).isFalse();
+
+        assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                Build.VERSION.SDK_INT)).isTrue();
+        assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                Build.VERSION.SDK_INT + 1)).isFalse();
+        assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT)).isTrue();
+        assertThat(Car.isApiAndPlatformVersionAtLeast(Car.API_VERSION_MAJOR_INT,
+                Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT + 1)).isFalse();
+    }
+
     private static void assertConnectedCar(Car car) {
         assertThat(car).isNotNull();
         assertThat(car.isConnected()).isTrue();
diff --git a/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java b/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java
new file mode 100644
index 0000000..ac009e0
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/EvChargingConnectorTypeTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.VehiclePropertyIds;
+import android.car.cts.utils.VehiclePropertyUtils;
+import android.car.hardware.property.EvChargingConnectorType;
+
+import org.junit.Test;
+
+import java.util.List;
+
+
+public class EvChargingConnectorTypeTest {
+
+    /**
+     * Test for {@link EvChargingConnectorType#toString()}
+     */
+    @Test
+    public void testToString() {
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.UNKNOWN))
+                .isEqualTo("UNKNOWN");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_1_AC))
+                .isEqualTo("IEC_TYPE_1_AC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_2_AC))
+                .isEqualTo("IEC_TYPE_2_AC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_3_AC))
+                .isEqualTo("IEC_TYPE_3_AC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_4_DC))
+                .isEqualTo("IEC_TYPE_4_DC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_1_CCS_DC))
+                .isEqualTo("IEC_TYPE_1_CCS_DC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.IEC_TYPE_2_CCS_DC))
+                .isEqualTo("IEC_TYPE_2_CCS_DC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_HPWC))
+                .isEqualTo("TESLA_HPWC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_ROADSTER))
+                .isEqualTo("TESLA_ROADSTER");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.TESLA_SUPERCHARGER))
+                .isEqualTo("TESLA_SUPERCHARGER");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.GBT_AC))
+                .isEqualTo("GBT_AC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.GBT_DC))
+                .isEqualTo("GBT_DC");
+        assertThat(EvChargingConnectorType.toString(EvChargingConnectorType.OTHER))
+                .isEqualTo("OTHER");
+        assertThat(EvChargingConnectorType.toString(0x999)).isEqualTo("0x999");
+    }
+
+    /**
+     * Test if all system properties have a mapped string value.
+     */
+    @Test
+    public void testAllConnectorTypesAreMappedInToString() {
+        List<Integer> connectorTypes =
+                VehiclePropertyUtils.getIntegersFromDataEnums(EvChargingConnectorType.class);
+        for (int connectorType : connectorTypes) {
+            String propertyString = EvChargingConnectorType.toString(connectorType);
+            assertThat(propertyString.startsWith("0x")).isFalse();
+        }
+    }
+}
diff --git a/tests/tests/car/src/android/car/cts/VehicleGearTest.java b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
index 5601757..319c7ed 100644
--- a/tests/tests/car/src/android/car/cts/VehicleGearTest.java
+++ b/tests/tests/car/src/android/car/cts/VehicleGearTest.java
@@ -19,13 +19,10 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.car.VehicleGear;
-import android.car.VehiclePropertyIds;
-import android.util.Log;
+import android.car.cts.utils.VehiclePropertyUtils;
 
 import org.junit.Test;
 
-import java.lang.reflect.Field;
-import java.util.ArrayList;
 import java.util.List;
 
 public class VehicleGearTest {
@@ -60,25 +57,10 @@
      */
     @Test
     public void testAllGearsAreMappedInToString() {
-        List<Integer> gears = getIntegersFromDataEnums(VehicleGear.class);
+        List<Integer> gears = VehiclePropertyUtils.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/VehicleIgnitionStateTest.java b/tests/tests/car/src/android/car/cts/VehicleIgnitionStateTest.java
new file mode 100644
index 0000000..fe4e14c
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/VehicleIgnitionStateTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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 static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.VehicleIgnitionState;
+import android.car.cts.utils.VehiclePropertyUtils;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public final class VehicleIgnitionStateTest {
+
+    @Test
+    public void testToString() {
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.UNDEFINED))
+                .isEqualTo("UNDEFINED");
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.LOCK)).isEqualTo("LOCK");
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.OFF)).isEqualTo("OFF");
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.ACC)).isEqualTo("ACC");
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.ON)).isEqualTo("ON");
+        assertThat(VehicleIgnitionState.toString(VehicleIgnitionState.START)).isEqualTo("START");
+    }
+
+    @Test
+    public void testAllIgnitionStatesAreMappedInToString() {
+        List<Integer> ignitionStates =
+                VehiclePropertyUtils.getIntegersFromDataEnums(VehicleIgnitionState.class);
+        for (Integer ignitionState : ignitionStates) {
+            String ignitionStateString = VehicleIgnitionState.toString(ignitionState);
+            assertWithMessage("%s starts with 0x", ignitionStateString)
+                    .that(ignitionStateString.startsWith("0x")).isFalse();
+        }
+    }
+}
+
diff --git a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
index 4d00ea9..58d7274 100644
--- a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -19,40 +19,21 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import android.car.VehiclePropertyIds;
+import android.car.cts.utils.VehiclePropertyUtils;
 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";
-
-    // Get all enums from the class.
-    private static List<Integer> getIntegersFromDataEnums() {
-        Field[] fields = VehiclePropertyIds.class.getDeclaredFields();
-        List<Integer> integerList = new ArrayList<>(5);
-        for (Field f : fields) {
-            if (f.getType() == int.class) {
-                try {
-                    integerList.add(f.getInt(VehiclePropertyIds.class));
-                } catch (IllegalAccessException | RuntimeException e) {
-                    Log.w(TAG, "Failed to get value");
-                }
-            }
-        }
-        return integerList;
-    }
 
     /**
      * Test for {@link VehiclePropertyIds#toString()}
@@ -331,7 +312,8 @@
      */
     @Test
     public void testAllPropertiesAreMappedInToString() {
-        List<Integer> systemProperties = getIntegersFromDataEnums();
+        List<Integer> systemProperties =
+                VehiclePropertyUtils.getIntegersFromDataEnums(VehiclePropertyIds.class);
         for (int propertyId : systemProperties) {
             String propertyString = VehiclePropertyIds.toString(propertyId);
             assertThat(propertyString.startsWith("0x")).isFalse();
diff --git a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.java b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.java
new file mode 100644
index 0000000..ad88306
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyUtils.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 android.car.cts.utils;
+
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+public class VehiclePropertyUtils {
+
+    private VehiclePropertyUtils() {
+    }
+
+    /** Returns all integer type enums from the class */
+    public 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(clazz.getSimpleName(), "Failed to get value");
+                }
+            }
+        }
+        return integerList;
+    }
+}
diff --git a/tests/tests/car_builtin/Android.bp b/tests/tests/car_builtin/Android.bp
new file mode 100644
index 0000000..2d2a179
--- /dev/null
+++ b/tests/tests/car_builtin/Android.bp
@@ -0,0 +1,47 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsCarBuiltinApiTestCases",
+    defaults: ["cts_defaults"],
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
+    resource_dirs: ["res"],
+    libs: [
+        "android.car.builtin",
+        "android.test.base",
+    ],
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.truth",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "cts-wm-util",
+        "testng",
+    ],
+
+    platform_apis: true,
+    sdk_version: "module_current",
+}
diff --git a/tests/tests/car_builtin/AndroidManifest.xml b/tests/tests/car_builtin/AndroidManifest.xml
new file mode 100644
index 0000000..56f46ba
--- /dev/null
+++ b/tests/tests/car_builtin/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<?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.car.cts.builtin">
+    <uses-feature android:name="android.hardware.type.automotive" />
+
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
+
+    <application android:description="@string/app_description">
+        <uses-library android:name="android.test.runner" />
+
+        <activity android:name="android.car.cts.builtin.activity.SimpleActivity"
+                  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.car.cts.builtin.activity.VirtualDisplayIdTestActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <service android:name="android.car.cts.builtin.os.SharedMemoryTestService"
+            android:process=":shdmemservice">
+        </service>
+        <service android:name="android.car.cts.builtin.os.ServiceManagerTestService"
+            android:process=":testservice">
+        </service>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.car.cts.builtin"
+                     android:label="CTS tests for car builtin api">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/car_builtin/AndroidTest.xml b/tests/tests/car_builtin/AndroidTest.xml
new file mode 100644
index 0000000..9532903
--- /dev/null
+++ b/tests/tests/car_builtin/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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 car builtin api test cases">
+    <object class="com.android.tradefed.testtype.suite.module.CarModuleController"
+            type="module_controller"/>
+    <option name="test-suite-tag" value="cts"/>
+    <option name="config-descriptor:metadata" key="component" value="auto"/>
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app"/>
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi"/>
+
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="CtsCarBuiltinApiTestCases.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.car.cts.builtin"/>
+    </test>
+</configuration>
diff --git a/tests/tests/car_builtin/OWNERS b/tests/tests/car_builtin/OWNERS
new file mode 100644
index 0000000..a8e927d
--- /dev/null
+++ b/tests/tests/car_builtin/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 526266
+keunyoung@google.com
+felipeal@google.com
+gurunagarajan@google.com
diff --git a/tests/tests/car_builtin/res/values/strings.xml b/tests/tests/car_builtin/res/values/strings.xml
new file mode 100644
index 0000000..3c9ef64
--- /dev/null
+++ b/tests/tests/car_builtin/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?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.
+-->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_description">CTS Tests for Car Builtin APIs</string>
+</resources>
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java
new file mode 100644
index 0000000..a323b22
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/CarBuiltinTest.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.CarBuiltin;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class CarBuiltinTest {
+    @Test
+    public void testMinorVersion() {
+        assertThat(CarBuiltin.API_VERSION_MINOR_INT).isAtLeast(0);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java
new file mode 100644
index 0000000..fdc07b7
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/PermissionHelperTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.PermissionHelper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class PermissionHelperTest {
+
+    private static final String EXPECTED_MONITOR_INPUT_PERMISSION_STRING =
+            "android.permission.MONITOR_INPUT";
+
+    @Test
+    public void testMonitorInputPermissionString() {
+        assertThat(PermissionHelper.MONITOR_INPUT)
+                .isEqualTo(EXPECTED_MONITOR_INPUT_PERMISSION_STRING);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.java b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.java
new file mode 100644
index 0000000..d111f71
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/SimpleActivity.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.car.cts.builtin.activity;
+
+import android.app.Activity;
+
+/**
+ * A very simple activity for testing PackageManagerHelper.
+ */
+public final class SimpleActivity extends Activity {
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java
new file mode 100644
index 0000000..03542eb
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/activity/VirtualDisplayIdTestActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.builtin.activity;
+
+import android.app.Activity;
+import android.car.builtin.content.ContextHelper;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Display;
+
+public final class VirtualDisplayIdTestActivity extends Activity {
+    public static final String TAG = VirtualDisplayIdTestActivity.class.getSimpleName();
+
+    private static int sDisplayId = Display.INVALID_DISPLAY;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sDisplayId = ContextHelper.getDisplayId(this);
+        Log.d(TAG, "onCreate: sDisplayId=" + sDisplayId);
+    }
+
+    public static int getDisplayId() {
+        return sDisplayId;
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
new file mode 100644
index 0000000..4e0919c
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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.builtin.app;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.app.ActivityManagerHelper;
+import android.content.pm.PackageManager;
+import android.os.Process;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ActivityManagerHelperTest {
+
+    private static final String TAG = ActivityManagerHelperTest.class.getSimpleName();
+
+    private static final String NOT_REQUESTED_PERMISSION_CAR_MILEAGE =
+            "android.car.permission.CAR_MILEAGE";
+    private static final String NOT_REQUESTED_PERMISSION_READ_CAR_POWER_POLICY =
+            "android.car.permission.READ_CAR_POWER_POLICY";
+
+    private static final String GRANTED_PERMISSION_INTERACT_ACROSS_USERS =
+            "android.permission.INTERACT_ACROSS_USERS";
+
+    private static final int OWNING_UID = -1;
+
+    @Test
+    public void testCheckComponentPermission() throws Exception {
+        // not requested from Manifest
+        assertComponentPermissionNotGranted(NOT_REQUESTED_PERMISSION_CAR_MILEAGE);
+        assertComponentPermissionNotGranted(NOT_REQUESTED_PERMISSION_READ_CAR_POWER_POLICY);
+
+        // requested from Manifest and granted
+        assertComponentPermissionGranted(GRANTED_PERMISSION_INTERACT_ACROSS_USERS);
+    }
+
+    //TODO(b/201005730): add as many as feasible test cases to cover more
+    // ActivityManagerHelper APIs.
+
+    private void assertComponentPermissionGranted(String permission) throws Exception {
+        assertThat(ActivityManagerHelper.checkComponentPermission(permission,
+                Process.myUid(), /* owningUid= */ OWNING_UID, /* exported= */ true))
+                .isEqualTo(PackageManager.PERMISSION_GRANTED);
+    }
+
+    private void assertComponentPermissionNotGranted(String permission) throws Exception {
+        assertThat(ActivityManagerHelper.checkComponentPermission(permission,
+                Process.myUid(), /* owningUid= */ OWNING_UID, /* exported= */ true))
+                .isEqualTo(PackageManager.PERMISSION_DENIED);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java
new file mode 100644
index 0000000..b2448c3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/DisplayUtils.java
@@ -0,0 +1,125 @@
+/*
+ * 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.builtin.app;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.media.ImageReader;
+import android.os.Looper;
+import android.util.DisplayMetrics;
+import android.view.Display;
+
+import com.android.compatibility.common.util.TestUtils;
+
+/**
+ * Utilities needed when interacting with the display
+ */
+public class DisplayUtils {
+    private static final int DISPLAY_ADDED_TIMEOUT_MS = 10_000;
+    private static final String CONTEXT_HELPER_CTS_DISPLAY_NAME = "ContextHelperCtsVirtualDisplay";
+
+    public static class VirtualDisplaySession implements AutoCloseable {
+        private VirtualDisplay mVirtualDisplay;
+        private ImageReader mReader;
+
+        /**
+         * Creates a virtual display having same size with default display and waits until it's
+         * in display list. The density of the virtual display is based on
+         * {@link DisplayMetrics#xdpi} so that the threshold of gesture detection is same as
+         * the default display's.
+         *
+         * @param context
+         * @param isPrivate if this display is a private display.
+         * @return virtual display.
+         *
+         * @throws IllegalStateException if called from main thread.
+         */
+        public Display createDisplayWithDefaultDisplayMetricsAndWait(Context context,
+                boolean isPrivate) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                throw new IllegalStateException("Should not call from main thread");
+            }
+
+            if (mReader != null) {
+                throw new IllegalStateException(
+                        "Only one display can be created during this session.");
+            }
+
+            DisplayManager displayManager = context.getSystemService(DisplayManager.class);
+            DisplayMetrics metrics = new DisplayMetrics();
+            displayManager.getDisplay(DEFAULT_DISPLAY).getRealMetrics(metrics);
+
+            mReader = ImageReader.newInstance(metrics.widthPixels, metrics.heightPixels,
+                    PixelFormat.RGBA_8888, 1 /* maxImages */);
+
+            Object waitObject = new Object();
+            DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
+                @Override
+                public void onDisplayAdded(int i) {
+                    synchronized (waitObject) {
+                        waitObject.notifyAll();
+                    }
+                }
+
+                @Override
+                public void onDisplayRemoved(int i) {
+                }
+
+                @Override
+                public void onDisplayChanged(int i) {
+                }
+            };
+
+            displayManager.registerDisplayListener(listener, null);
+
+            int flags = isPrivate ? 0
+                    : (VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC);
+
+            mVirtualDisplay = displayManager.createVirtualDisplay(CONTEXT_HELPER_CTS_DISPLAY_NAME,
+                    metrics.widthPixels, metrics.heightPixels, (int) metrics.xdpi,
+                    mReader.getSurface(), flags);
+
+            try {
+                int theNewDisplayId = mVirtualDisplay.getDisplay().getDisplayId();
+                TestUtils.waitOn(waitObject,
+                        () -> displayManager.getDisplay(theNewDisplayId) != null,
+                        DISPLAY_ADDED_TIMEOUT_MS,
+                        String.format("wait for virtual display %d adding", theNewDisplayId));
+            } finally {
+                displayManager.unregisterDisplayListener(listener);
+            }
+
+            return mVirtualDisplay.getDisplay();
+        }
+
+        @Override
+        public void close() {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.release();
+            }
+            if (mReader != null) {
+                mReader.close();
+            }
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.java
new file mode 100644
index 0000000..6cc2071
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/KeyguardManagerHelperTest.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.car.cts.builtin.app;
+
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.app.KeyguardManagerHelper;
+import android.server.wm.ActivityManagerTestBase;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class KeyguardManagerHelperTest extends ActivityManagerTestBase {
+
+    private static final String TAG = KeyguardManagerHelperTest.class.getSimpleName();
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        assumeTrue(supportsSecureLock());
+    }
+
+    @Test
+    public void testIsKeyguardLocked() throws Exception {
+        try (LockScreenSession lockScreenSession = createManagedLockScreenSession()) {
+            lockScreenSession.setLockCredential().gotoKeyguard();
+            assertThat(KeyguardManagerHelper.isKeyguardLocked()).isTrue();
+
+            unlockDevice();
+            lockScreenSession.enterAndConfirmLockCredential();
+            mWmState.waitAndAssertKeyguardGone();
+            assertThat(KeyguardManagerHelper.isKeyguardLocked()).isFalse();
+        }
+    }
+
+    private void unlockDevice() {
+        touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY);
+        pressUnlockButton();
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java
new file mode 100644
index 0000000..927f9a6
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/content/ContextHelperTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.builtin.content;
+
+import static android.car.cts.builtin.app.DisplayUtils.VirtualDisplaySession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.content.ContextHelper;
+import android.car.cts.builtin.activity.VirtualDisplayIdTestActivity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.server.wm.ActivityManagerTestBase;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ContextHelperTest extends ActivityManagerTestBase {
+    private static final int ACTIVITY_FOCUS_TIMEOUT_MS = 10_000;
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testDefaultDisplayId() throws Exception {
+        // execution and assertion
+        assertThat(ContextHelper.getDisplayId(mContext)).isEqualTo(Display.DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public void testVirtualDisplayId() throws Exception {
+        // check the assumption
+        String requiredFeature = PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(requiredFeature));
+
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            // setup: create a virtual display
+            int createdVirtualDisplayId = session
+                    .createDisplayWithDefaultDisplayMetricsAndWait(mContext, true).getDisplayId();
+
+            assertNotEquals(createdVirtualDisplayId, Display.DEFAULT_DISPLAY);
+            assertNotEquals(createdVirtualDisplayId, Display.INVALID_DISPLAY);
+
+            // execution: launch VirtualDisplayIdActivity in the virtual display
+            launchVirtualDisplayIdTestActivity(createdVirtualDisplayId, mContext.getPackageName(),
+                    VirtualDisplayIdTestActivity.class.getName());
+
+            // assertion
+            assertEquals(createdVirtualDisplayId, VirtualDisplayIdTestActivity.getDisplayId());
+        }
+    }
+
+    private void launchVirtualDisplayIdTestActivity(int displayId,
+            String pkgName, String activityClassName) {
+        ComponentName testActivity = new ComponentName(pkgName, activityClassName);
+        launchActivityOnDisplay(testActivity, displayId);
+        waitForActivityFocused(ACTIVITY_FOCUS_TIMEOUT_MS, testActivity);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java
new file mode 100644
index 0000000..4b2be89
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/content/pm/PackageManagerHelperTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.builtin.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.content.pm.PackageManagerHelper;
+import android.car.cts.builtin.R;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public final class PackageManagerHelperTest {
+
+    private static final String TAG = PackageManagerHelperTest.class.getSimpleName();
+    private static final String ANDROID_CAR_PKG = "com.android.car";
+    private static final String CAR_BUILTIN_CTS_PKG = "android.car.cts.builtin";
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    private final PackageManager mPackageManager = mContext.getPackageManager();
+
+    @Test
+    public void testGetPackageInfoAsUser() throws Exception {
+        // setup
+        String expectedActivityName = "android.car.cts.builtin.activity.SimpleActivity";
+        int flags = PackageManager.GET_ACTIVITIES | PackageManager.GET_INSTRUMENTATION
+                | PackageManager.GET_SERVICES;
+        int curUser = UserHandle.myUserId();
+
+        // execution
+        PackageInfo pkgInfoUser = PackageManagerHelper.getPackageInfoAsUser(mPackageManager,
+                CAR_BUILTIN_CTS_PKG, flags, curUser);
+        ApplicationInfo appInfo = pkgInfoUser.applicationInfo;
+        ActivityInfo[] activities = pkgInfoUser.activities;
+        ServiceInfo[] services = pkgInfoUser.services;
+
+        // assertion
+        assertThat(appInfo).isNotNull();
+        assertThat(appInfo.descriptionRes).isEqualTo(R.string.app_description);
+        assertThat(activities).isNotNull();
+        assertThat(hasActivity(expectedActivityName, activities)).isTrue();
+        assertThat(services).isNotNull();
+    }
+
+    @Test
+    public void testAppTypeChecking() throws Exception {
+        // setup
+        ApplicationInfo systemApp = mPackageManager
+                .getApplicationInfo(ANDROID_CAR_PKG, /* flags= */ 0);
+        ApplicationInfo ctsApp = mPackageManager
+                .getApplicationInfo(CAR_BUILTIN_CTS_PKG, /* flags= */ 0);
+
+        // execution and assertion
+        assertThat(PackageManagerHelper.isSystemApp(systemApp)).isTrue();
+        assertThat(PackageManagerHelper.isUpdatedSystemApp(systemApp)).isFalse();
+        assertThat(PackageManagerHelper.isSystemExtApp(systemApp)).isFalse();
+        assertThat(PackageManagerHelper.isOemApp(systemApp)).isFalse();
+        assertThat(PackageManagerHelper.isOdmApp(systemApp)).isFalse();
+        assertThat(PackageManagerHelper.isVendorApp(systemApp)).isFalse();
+        assertThat(PackageManagerHelper.isProductApp(systemApp)).isFalse();
+
+        assertThat(PackageManagerHelper.isSystemApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isUpdatedSystemApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isSystemExtApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isOemApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isOdmApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isVendorApp(ctsApp)).isFalse();
+        assertThat(PackageManagerHelper.isProductApp(ctsApp)).isFalse();
+    }
+
+    //TODO (b/201822684) Add more test cases to cover more PackageManagerHelper APIs
+
+    private boolean hasActivity(String activityName, ActivityInfo[] activities) {
+        return Arrays.stream(activities).anyMatch(a -> activityName.equals(a.name));
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.java
new file mode 100644
index 0000000..aab86c7
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/BuildHelperTest.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.car.cts.builtin.os;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.os.BuildHelper;
+import android.os.Build;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class BuildHelperTest {
+
+    private static final String TAG = BuildHelperTest.class.getSimpleName();
+    private static final String BUILD_TYPE_USER = "user";
+    private static final String BUILD_TYPE_ENG = "eng";
+    private static final String BUILD_TYPE_USER_DEBUG = "userdebug";
+
+    @Test
+    public void testBuildTypeCheck() throws Exception {
+        switch (Build.TYPE) {
+            case BUILD_TYPE_USER:
+                assertTrue(BuildHelper.isUserBuild());
+                assertFalse(BuildHelper.isUserDebugBuild());
+                assertFalse(BuildHelper.isEngBuild());
+                assertFalse(BuildHelper.isDebuggableBuild());
+                break;
+            case BUILD_TYPE_USER_DEBUG:
+                assertFalse(BuildHelper.isUserBuild());
+                assertTrue(BuildHelper.isUserDebugBuild());
+                assertFalse(BuildHelper.isEngBuild());
+                assertTrue(BuildHelper.isDebuggableBuild());
+                break;
+            case BUILD_TYPE_ENG:
+                assertFalse(BuildHelper.isUserBuild());
+                assertFalse(BuildHelper.isUserDebugBuild());
+                assertTrue(BuildHelper.isEngBuild());
+                assertTrue(BuildHelper.isDebuggableBuild());
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown Build Type: " + Build.TYPE);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl b/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl
new file mode 100644
index 0000000..52aa34d
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/IServiceManagerTestService.aidl
@@ -0,0 +1,21 @@
+/*
+ * 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.builtin.os;
+
+interface IServiceManagerTestService {
+    int echo(in int val);
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl
new file mode 100644
index 0000000..b36aecde
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ISharedMemoryTestService.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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.builtin.os;
+
+import android.os.ParcelFileDescriptor;
+
+interface ISharedMemoryTestService {
+    int readBufData(in ParcelFileDescriptor pfd);
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java
new file mode 100644
index 0000000..b46e26d
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ParcelHelperTest.java
@@ -0,0 +1,91 @@
+/*
+ * 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.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.ParcelHelper;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.ArraySet;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.Arrays;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class ParcelHelperTest {
+
+    private static final String TAG = ParcelHelperTest.class.getSimpleName();
+    private final String[] mInitData = {"Hello, ", "Android", "Auto", "!"};
+
+    @Test
+    public void testBlobAccess() {
+        String blobContent = "Hello, Android Auto!";
+        Parcel p = Parcel.obtain();
+
+        // setup
+        byte[] inBlob = blobContent.getBytes();
+        ParcelHelper.writeBlob(p, inBlob);
+        p.setDataPosition(0);
+
+        // execution
+        byte[] outBlob = ParcelHelper.readBlob(p);
+
+        // assertion
+        assertThat(outBlob).isEqualTo(inBlob);
+    }
+
+    @Test
+    public void testArraySetAccess() {
+        ArraySet<Object> inputSet = new ArraySet<>(mInitData);
+        ArraySet<?> outputSet = null;
+        Parcel p = Parcel.obtain();
+
+        // setup
+        ArraySet emptySet = ParcelHelper.readArraySet(p, /* loader = */ null);
+        assertThat(emptySet.size()).isEqualTo(0);
+        ParcelHelper.writeArraySet(p, inputSet);
+        p.setDataPosition(0);
+
+        // execution
+        outputSet = ParcelHelper.readArraySet(p, String.class.getClassLoader());
+
+        // assertion
+        assertThat(outputSet).containsExactlyElementsIn(inputSet);
+    }
+
+    @Test
+    public void testStringArrayAccess() {
+        String[] inputArray = mInitData;
+        Parcel p = Parcel.obtain();
+
+        // setup
+        p.writeStringArray(inputArray);
+        p.setDataPosition(0);
+
+        // execution
+        String[] outputArray = ParcelHelper.readStringArray(p);
+
+        // assertion
+        assertThat(outputArray).isEqualTo(inputArray);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java
new file mode 100644
index 0000000..36e76be
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerHelperTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.builtin.os;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.ServiceManagerHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ServiceManagerHelperTest {
+
+    private static final String SERVICE_PACKAGE_NAME = "android.car.cts.builtin";
+
+    private static final String[] SYSTEM_SERVICES = {
+        Context.ACTIVITY_SERVICE,
+        Context.ALARM_SERVICE,
+        Context.AUDIO_SERVICE,
+        Context.DISPLAY_SERVICE,
+        Context.INPUT_SERVICE,
+        Context.JOB_SCHEDULER_SERVICE,
+        Context.LOCATION_SERVICE,
+        Context.POWER_SERVICE,
+        Context.WINDOW_SERVICE
+    };
+    private static final int TIMEOUT = 20_000;
+
+    private Instrumentation mInstrumentation;
+    private PeerConnection mRemoteConnection;
+    private IServiceManagerTestService mRemoteService;
+
+    public static class PeerConnection implements ServiceConnection {
+        private final CountDownLatch mServiceReadyLatch = new CountDownLatch(1);
+
+        private IServiceManagerTestService mService;
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mService = IServiceManagerTestService.Stub.asInterface(service);
+            mServiceReadyLatch.countDown();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        public void waitForServiceReady() throws Exception {
+            mServiceReadyLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+
+        public IServiceManagerTestService getService() {
+            return mService;
+        }
+    }
+
+    @Test
+    public void testInitServiceCache() throws Exception {
+        // The service manager keeps a cache of system services during
+        // initialization
+        for (String serviceName : SYSTEM_SERVICES) {
+            assertNotNull(ServiceManagerHelper.checkService(serviceName));
+            assertNotNull(ServiceManagerHelper.getService(serviceName));
+        }
+    }
+
+    @Test
+    public void testServiceCacheWithCreatedService() throws Exception {
+        // setup a new service
+        String testServiceName = ServiceManagerTestService.class.getName();
+        Random randomGenerator = new Random();
+        int maxCount = 10;
+
+        setUpService();
+
+        // assert the new service works
+        for (int i = 0; i < maxCount; i++) {
+            int val = randomGenerator.nextInt();
+            assertEquals(val, mRemoteService.echo(val));
+        }
+
+        // assert the newly created service is not in the ServiceManager cache
+        assertNull(ServiceManagerHelper.checkService(testServiceName));
+        assertNull(ServiceManagerHelper.getService(testServiceName));
+    }
+
+    private void setUpService() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = mInstrumentation.getContext();
+        // Bring up both remote processes and wire them to each other
+        Intent remoteIntent = new Intent();
+        remoteIntent.setComponent(new ComponentName(SERVICE_PACKAGE_NAME,
+                ServiceManagerTestService.class.getName()));
+        mRemoteConnection = new PeerConnection();
+        getContext().bindService(remoteIntent, mRemoteConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+
+        mRemoteConnection.waitForServiceReady();
+        mRemoteService = mRemoteConnection.getService();
+        assertNotNull(mRemoteService);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java
new file mode 100644
index 0000000..8a8d15a
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/ServiceManagerTestService.java
@@ -0,0 +1,36 @@
+/*
+ * 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.builtin.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+public final class ServiceManagerTestService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new ServiceImpl();
+    }
+
+    private static class ServiceImpl extends IServiceManagerTestService.Stub {
+        @Override
+        public int echo(int val) throws RemoteException {
+            return val;
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.java
new file mode 100644
index 0000000..7f1c517
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryFileDescriptorTest.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 android.car.cts.builtin.os;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.SharedMemoryHelper;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.MediumTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SharedMemoryFileDescriptorTest {
+
+    private static final int TIMEOUT = 20_000;
+    private Instrumentation mInstrumentation;
+    private Intent mRemoteIntent;
+    private PeerConnection mRemoteConnection;
+    private ISharedMemoryTestService mRemote;
+
+    public static class PeerConnection implements ServiceConnection {
+        private final CountDownLatch mServiceReadyLatch = new CountDownLatch(1);
+
+        private ISharedMemoryTestService mTestService = null;
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            mTestService = ISharedMemoryTestService.Stub.asInterface(service);
+            mServiceReadyLatch.countDown();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+        }
+
+        public ISharedMemoryTestService get() throws Exception {
+            mServiceReadyLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+            return mTestService;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Context context = mInstrumentation.getContext();
+        // Bring up both remote processes and wire them to each other
+        mRemoteIntent = new Intent();
+        mRemoteIntent.setComponent(new ComponentName(
+                "android.car.cts.builtin", "android.car.cts.builtin.os.SharedMemoryTestService"));
+        mRemoteConnection = new PeerConnection();
+        getContext().bindService(mRemoteIntent, mRemoteConnection,
+                Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
+
+        mRemote = mRemoteConnection.get();
+        assertNotNull(mRemote);
+    }
+
+    @After
+    public void tearDown() {
+        Context context = mInstrumentation.getContext();
+        context.unbindService(mRemoteConnection);
+    }
+
+    @Test
+    public void testReadBufData() throws RemoteException, ErrnoException, IOException {
+        // setup
+        int memSize = 32 * 1024;
+
+        SharedMemory sharedMem = SharedMemory.create(/* name */ null, memSize);
+        ByteBuffer buffer = null;
+        try {
+            buffer = sharedMem.mapReadWrite();
+            int checksum = 0;
+            for (int i = 0; i < memSize; i++) {
+                buffer.put((byte) i);
+                checksum += (byte) i;
+            }
+
+            // execution
+            ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sharedMem);
+            int returnedChecksum = mRemote.readBufData(pfd);
+
+            // assertion
+            assertEquals(checksum, returnedChecksum);
+        } finally {
+            // teardown
+            SharedMemory.unmap(buffer);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.java
new file mode 100644
index 0000000..a231bbe
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryHelperTest.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.car.cts.builtin.os;
+
+import static org.junit.Assert.assertEquals;
+
+import android.car.builtin.os.SharedMemoryHelper;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelFileDescriptor.AutoCloseInputStream;
+import android.os.SharedMemory;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+public final class SharedMemoryHelperTest {
+    private static final String TAG = SharedMemoryHelperTest.class.getSimpleName();
+
+    @Test
+    public void testCreateParcelFileDescriptor() throws Exception {
+        // setup
+        int memSize = 32 * 1024;
+        int bufSize = memSize / 4;
+        SharedMemory sm = SharedMemory.create(/* name */ null, memSize);
+        ByteBuffer bb = sm.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+        populateBufferSequentially(bb, bufSize);
+
+        // execution
+        ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sm);
+
+        // assertion
+        try (AutoCloseInputStream in = new AutoCloseInputStream(pfd)) {
+            bb.rewind();
+            for (int i = 0; i < bufSize; i++) {
+                assertEquals(bb.get(), (byte) in.read());
+            }
+        }
+
+        // teardown
+        SharedMemory.unmap(bb);
+    }
+
+    @Test
+    public void testSharedMemoryCreation() throws Exception {
+        // setup
+        int memSize = 32 * 1024;
+        int bufSize = memSize / 4;
+        SharedMemory sm1 = SharedMemory.create(/* name */ null, memSize);
+        ByteBuffer bb1 = sm1.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+        populateBufferSequentially(bb1, bufSize);
+
+        // execution
+        ParcelFileDescriptor pfd = SharedMemoryHelper.createParcelFileDescriptor(sm1);
+        Parcel p = Parcel.obtain();
+        p.writeFileDescriptor(pfd.getFileDescriptor());
+        p.setDataPosition(0);
+        SharedMemory sm2 = SharedMemory.CREATOR.createFromParcel(p);
+        ByteBuffer bb2 = sm2.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, 0, bufSize);
+
+        // assertion
+        bb1.rewind();
+        assertEquals(bb1, bb2);
+
+        // teardown
+        SharedMemory.unmap(bb1);
+        SharedMemory.unmap(bb2);
+    }
+
+    private void populateBufferSequentially(ByteBuffer buf, int length) {
+        for (int i = 0; i < length; i++) {
+            buf.put((byte) i);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java
new file mode 100644
index 0000000..7f95ec8
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SharedMemoryTestService.java
@@ -0,0 +1,57 @@
+/*
+ * 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.builtin.os;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.os.SharedMemory;
+import android.system.ErrnoException;
+
+import java.nio.ByteBuffer;
+
+public class SharedMemoryTestService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new ServiceImpl();
+    }
+
+    private static class ServiceImpl extends ISharedMemoryTestService.Stub {
+        @Override
+        public int readBufData(ParcelFileDescriptor pfd) throws RemoteException {
+            int checksum = 0;
+            SharedMemory sharedMemory = null;
+            ByteBuffer mappedBuffer = null;
+
+            try {
+                sharedMemory = SharedMemory.fromFileDescriptor(pfd);
+                mappedBuffer = sharedMemory.mapReadOnly();
+                for (int i = 0; i < sharedMemory.getSize(); i++) {
+                    checksum += mappedBuffer.get();
+                }
+            } catch (ErrnoException ex) {
+                throw new RuntimeException(ex);
+            } finally {
+                SharedMemory.unmap(mappedBuffer);
+            }
+
+            return checksum;
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
new file mode 100644
index 0000000..9b915f3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/SystemPropertiesHelperTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.SystemPropertiesHelper;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class SystemPropertiesHelperTest {
+    private static final String TAG = SystemPropertiesHelperTest.class.getSimpleName();
+
+    // a temporary SystemProperty for CTS. it will be cleared after device reboot.
+    private static final String CTS_TEST_PROPERTY_KEY = "cts.car.builtin_property_helper.String";
+    private static final String CTS_TEST_PROPERTY_VAL = "SystemPropertiesHelperTest";
+
+    @Test
+    public void testSet() {
+        SystemPropertiesHelper.set(CTS_TEST_PROPERTY_KEY, CTS_TEST_PROPERTY_VAL);
+        String val = SystemProperties.get(CTS_TEST_PROPERTY_KEY);
+        Log.d(TAG, val);
+        assertThat(val).isEqualTo(CTS_TEST_PROPERTY_VAL);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java
new file mode 100644
index 0000000..2bdd069
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/TraceHelperTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.os.TraceHelper;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class TraceHelperTest {
+
+    // the value is from frameworks/base/core/java/android/os/Trace.java
+    private static final long EXPECTED_CAR_SERVICE_TRACE_TAG = 1L << 19;
+
+    @Test
+    public void testCarServiceTraceTag() {
+        assertThat(TraceHelper.TRACE_TAG_CAR_SERVICE).isEqualTo(EXPECTED_CAR_SERVICE_TRACE_TAG);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java
new file mode 100644
index 0000000..1a24cdd
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/os/UserManagerHelperTest.java
@@ -0,0 +1,95 @@
+/*
+ * 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.builtin.os;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.car.builtin.os.UserManagerHelper;
+import android.content.Context;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class UserManagerHelperTest {
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    private Context mContext;
+    private UserManager mUserManager;
+
+    @Before
+    public void setup() {
+        mContext = mInstrumentation.getContext();
+        mUserManager = mContext.getSystemService(UserManager.class);
+    }
+
+    @Test
+    public void testPreCreateUser_fullUser() {
+        preCreateUserTest(UserManager.USER_TYPE_FULL_SECONDARY);
+    }
+
+    @Test
+    public void testPreCreateUser_guestUser() {
+        preCreateUserTest(UserManager.USER_TYPE_FULL_GUEST);
+    }
+
+    private void preCreateUserTest(String type) {
+        try {
+            mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    android.Manifest.permission.CREATE_USERS);
+
+            UserHandle user = UserManagerHelper.preCreateUser(mUserManager, type);
+
+            assertPrecreatedUserExists(user, type);
+        } finally {
+            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private void assertPrecreatedUserExists(UserHandle user, String type) {
+        assertThat(user).isNotNull();
+        try {
+            String allUsers = SystemUtil.runShellCommand("cmd user list --all -v");
+            String[] result = allUsers.split("\n");
+            for (int i = 0; i < result.length; i++) {
+                if (result[i].contains("id=" + user.getIdentifier())) {
+                    assertThat(result[i]).contains("(pre-created)");
+                    if (type == UserManager.USER_TYPE_FULL_SECONDARY) {
+                        assertThat(result[i]).contains("type=full.SECONDARY");
+                    }
+                    if (type == UserManager.USER_TYPE_FULL_GUEST) {
+                        assertThat(result[i]).contains("type=full.GUEST");
+                    }
+                    return;
+                }
+            }
+            fail("User not found. All users: " + allUsers + ". Expected user: " + user);
+        } finally {
+            // Remove User
+            mUserManager.removeUser(user);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
new file mode 100644
index 0000000..8183afd
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AssistUtilsHelperTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.builtin.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.car.builtin.util.AssistUtilsHelper;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public final class AssistUtilsHelperTest {
+    private static final String TAG = AssistUtilsHelper.class.getSimpleName();
+    private static final String PERMISSION_ACCESS_VOICE_INTERACTION_SERVICE =
+            "android.permission.ACCESS_VOICE_INTERACTION_SERVICE";
+    private static final int TIMEOUT = 20_000;
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+    private final Context mContext = mInstrumentation.getContext();
+
+    @Test
+    public void testOnShownCallback() throws Exception {
+        try {
+            // setup
+            mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    PERMISSION_ACCESS_VOICE_INTERACTION_SERVICE);
+
+            // execution
+            SessionShowCallbackHelperImpl callbackHelperImpl = new SessionShowCallbackHelperImpl();
+            AssistUtilsHelper.showPushToTalkSessionForActiveService(mContext, callbackHelperImpl);
+            callbackHelperImpl.waitForCallbackLatch();
+
+            // assertion
+            assertTrue(callbackHelperImpl.isSessionOnShown());
+        } finally {
+            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private static class SessionShowCallbackHelperImpl implements
+            AssistUtilsHelper.VoiceInteractionSessionShowCallbackHelper {
+
+        private final CountDownLatch mCallbackLatch = new CountDownLatch(1);
+        private boolean mIsSessionOnShown = false;
+
+        public void onShown() {
+            mIsSessionOnShown = true;
+            Log.d(TAG, "onShown is called");
+            mCallbackLatch.countDown();
+        }
+
+        public void onFailed() {
+            Log.d(TAG, "onFailed");
+        }
+
+        private boolean isSessionOnShown() {
+            return mIsSessionOnShown;
+        }
+
+        private void waitForCallbackLatch() throws Exception {
+            mCallbackLatch.await(TIMEOUT, TimeUnit.MILLISECONDS);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java
new file mode 100644
index 0000000..1add30c
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/AtomicFileHelperTest.java
@@ -0,0 +1,167 @@
+/*
+ * 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.builtin.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.util.AtomicFileHelper;
+import android.content.Context;
+import android.util.AtomicFile;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+
+@RunWith(AndroidJUnit4.class)
+public final class AtomicFileHelperTest {
+    private static final String TAG = AtomicFileHelperTest.class.getSimpleName();
+
+    private static final String PRE_EXIST_TEST_FILE_NAME = "TestFilePreExist";
+    private static final String NEWLY_CREATED_TEST_FILE_NAME = "TestFileNewlyCreated";
+    private static final String FAILED_TEST_FILE_NAME = "TestFileFailed";
+    private static final String REWRITE_TEST_FILE_NAME = "TestFileRewriteCreated";
+    private static final String TEST_FILE_CONTENT = "AtomicFileHelper CTS TEST FILE CONTENT";
+    private static final String REWRITE_CONTENT = "CTS TEST ATOMIC FILE REWRITE";
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testAtomicFileWithPreExistedBaseFile() throws Exception {
+        File appPrivateFileDir = mContext.getFilesDir();
+
+        // create a private file
+        mContext.openFileOutput(PRE_EXIST_TEST_FILE_NAME, Context.MODE_PRIVATE);
+
+        // create a File object to point to the newly created private file
+        File baseFile = new File(/* parent= */appPrivateFileDir, PRE_EXIST_TEST_FILE_NAME);
+        AtomicFile atomicFile = new AtomicFile(baseFile);
+
+        // execution and assertion
+        assertTrue(AtomicFileHelper.exists(atomicFile));
+        baseFile.delete();
+        assertFalse(AtomicFileHelper.exists(atomicFile));
+    }
+
+    @Test
+    public void testAtomicFileWithNonExistedBaseFile() throws Exception {
+        // setup
+        File appPrivateFileDir = mContext.getFilesDir();
+        File baseFile = new File(/* parent= */appPrivateFileDir, NEWLY_CREATED_TEST_FILE_NAME);
+        if (baseFile.exists()) {
+            baseFile.delete();
+        }
+        AtomicFile atomicFile = new AtomicFile(baseFile);
+
+        // execution and assertion
+        // 1. the base file does not exist yet
+        assertFalse(AtomicFileHelper.exists(atomicFile));
+
+        // 2. write into the atomic file and result a new base file
+        FileOutputStream fos = atomicFile.startWrite();
+        fos.write(TEST_FILE_CONTENT.getBytes());
+        atomicFile.finishWrite(fos);
+        assertTrue(AtomicFileHelper.exists(atomicFile));
+
+        // 3. check if the content match by directly read from the base file
+        BufferedReader br = new BufferedReader(new FileReader(baseFile));
+        String fileContent = br.readLine();
+        Log.d(TAG, fileContent);
+        assertEquals(TEST_FILE_CONTENT, fileContent);
+        br.close();
+
+        // 4. delete the base file;
+        baseFile.delete();
+        assertFalse(AtomicFileHelper.exists(atomicFile));
+    }
+
+    @Test
+    public void testAtomicFileWithFailedWrite() throws Exception {
+        // setup
+        File appPrivateFileDir = mContext.getFilesDir();
+        File baseFile = new File(/* parent= */appPrivateFileDir, REWRITE_TEST_FILE_NAME);
+        if (baseFile.exists()) {
+            baseFile.delete();
+        }
+        AtomicFile atomicFile = new AtomicFile(baseFile);
+
+        // execution and assertion
+        // 1. the base file does not exist yet
+        assertFalse(AtomicFileHelper.exists(atomicFile));
+
+        // 2. write into the atomic file and result a new base file
+        FileOutputStream fos = atomicFile.startWrite();
+        fos.write(TEST_FILE_CONTENT.getBytes());
+        atomicFile.failWrite(fos);
+        assertFalse(AtomicFileHelper.exists(atomicFile));
+    }
+
+    @Test
+    public void testAtomicFileRewrite() throws Exception {
+        // setup
+        File appPrivateFileDir = mContext.getFilesDir();
+        File baseFile = new File(/* parent= */appPrivateFileDir, NEWLY_CREATED_TEST_FILE_NAME);
+        if (baseFile.exists()) {
+            baseFile.delete();
+        }
+
+        // execution and assertion
+        // 1. write into the atomic file and result a new base file
+        AtomicFile atomicFile1 = new AtomicFile(baseFile);
+        FileOutputStream fos1 = atomicFile1.startWrite();
+        fos1.write(TEST_FILE_CONTENT.getBytes());
+        atomicFile1.finishWrite(fos1);
+        assertTrue(AtomicFileHelper.exists(atomicFile1));
+        fos1.close();
+
+        // 2. create a new AtomicFile and rewrite new content
+        AtomicFile atomicFile2 = new AtomicFile(baseFile);
+        FileOutputStream fos2 = atomicFile2.startWrite();
+        fos2.write(REWRITE_CONTENT.getBytes());
+        atomicFile2.finishWrite(fos2);
+        assertTrue(AtomicFileHelper.exists(atomicFile1));
+        fos2.close();
+
+        // 3. reopen the atomic file and read out the content
+        AtomicFile atomicFile3 = new AtomicFile(baseFile);
+        FileInputStream fis = atomicFile3.openRead();
+        String atomicFileReadContent = new String(atomicFile3.readFully());
+        Log.d(TAG, atomicFileReadContent);
+        assertEquals(atomicFileReadContent, REWRITE_CONTENT);
+        fis.close();
+
+        // 4. test if AtomicFile read is the same as base file read
+        BufferedReader br = new BufferedReader(new FileReader(baseFile));
+        String baseFileReadContent = br.readLine();
+        Log.d(TAG, baseFileReadContent);
+        assertEquals(atomicFileReadContent, baseFileReadContent);
+        br.close();
+
+        // 5. delete the base file;
+        baseFile.delete();
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java
new file mode 100644
index 0000000..ba34cb3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/EventLogHelperTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.builtin.util;
+
+import android.car.builtin.util.EventLogHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public final class EventLogHelperTest {
+
+    private static final int TIMEOUT_MS = 60_000;
+    // All Eventlogs would be logged to event buffer.
+    private static final LogcatHelper.Buffer BUFFER = LogcatHelper.Buffer.EVENTS;
+
+    @Before
+    public void setup() {
+        LogcatHelper.clearLog();
+    }
+
+    @Test
+    public void testWriteCarHelperStart() {
+        EventLogHelper.writeCarHelperStart();
+
+        assertLogMessage("I car_helper_start:");
+    }
+
+    @Test
+    public void testWriteCarHelperBootPhase() {
+        EventLogHelper.writeCarHelperBootPhase(1);
+
+        assertLogMessage("I car_helper_boot_phase: 1");
+    }
+
+    @Test
+    public void testWriteCarHelperUserStarting() {
+        EventLogHelper.writeCarHelperUserStarting(100);
+
+        assertLogMessage("I car_helper_user_starting: 100");
+    }
+
+    @Test
+    public void testWriteCarHelperUserSwitching() {
+        EventLogHelper.writeCarHelperUserSwitching(100, 101);
+
+        assertLogMessage("I car_helper_user_switching: [100,101]");
+    }
+
+    @Test
+    public void testWriteCarHelperUserUnlocking() {
+        EventLogHelper.writeCarHelperUserUnlocking(100);
+
+        assertLogMessage("I car_helper_user_unlocking: 100");
+    }
+
+    @Test
+    public void testWriteCarHelperUserUnlocked() {
+        EventLogHelper.writeCarHelperUserUnlocked(100);
+
+        assertLogMessage("I car_helper_user_unlocked: 100");
+    }
+
+    @Test
+    public void testWriteCarHelperUserStopping() {
+        EventLogHelper.writeCarHelperUserStopping(100);
+
+        assertLogMessage("I car_helper_user_stopping: 100");
+    }
+
+    @Test
+    public void testWriteCarHelperUserStopped() {
+        EventLogHelper.writeCarHelperUserStopped(100);
+
+        assertLogMessage("I car_helper_user_stopped: 100");
+    }
+
+    @Test
+    public void testWriteCarHelperServiceConnected() {
+        EventLogHelper.writeCarHelperServiceConnected();
+
+        assertLogMessage("I car_helper_svc_connected");
+    }
+
+    @Test
+    public void testWriteCarServiceInit() {
+        EventLogHelper.writeCarServiceInit(101);
+
+        assertLogMessage("I car_service_init: 101");
+    }
+
+    @Test
+    public void testWriteCarServiceVhalReconnected() {
+        EventLogHelper.writeCarServiceVhalReconnected(101);
+
+        assertLogMessage("I car_service_vhal_reconnected: 101");
+    }
+
+    @Test
+    public void testWriteCarServiceSetCarServiceHelper() {
+        EventLogHelper.writeCarServiceSetCarServiceHelper(101);
+
+        assertLogMessage("I car_service_set_car_service_helper: 101");
+    }
+
+    @Test
+    public void tesWriteCarServiceOnUserLifecycle() {
+        EventLogHelper.writeCarServiceOnUserLifecycle(1, 2, 3);
+
+        assertLogMessage("I car_service_on_user_lifecycle: [1,2,3]");
+    }
+
+    @Test
+    public void testWriteCarServiceCreate() {
+        EventLogHelper.writeCarServiceCreate(true);
+
+        assertLogMessage("I car_service_create: 1");
+    }
+
+    @Test
+    public void testWriteCarServiceConnected() {
+        EventLogHelper.writeCarServiceConnected("testString");
+
+        assertLogMessage("I car_service_connected: testString");
+    }
+
+    @Test
+    public void testWriteCarServiceDestroy() {
+        EventLogHelper.writeCarServiceDestroy(true);
+
+        assertLogMessage("I car_service_destroy: 1");
+    }
+
+    @Test
+    public void testWriteCarServiceVhalDied() {
+        EventLogHelper.writeCarServiceVhalDied(101);
+
+        assertLogMessage("I car_service_vhal_died: 101");
+    }
+
+    @Test
+    public void testWriteCarServiceInitBootUser() {
+        EventLogHelper.writeCarServiceInitBootUser();
+
+        assertLogMessage("I car_service_init_boot_user");
+    }
+
+    @Test
+    public void testWriteCarServiceOnUserRemoved() {
+        EventLogHelper.writeCarServiceOnUserRemoved(101);
+
+        assertLogMessage("I car_service_on_user_removed: 101");
+    }
+
+    @Test
+    public void testWriteCarUserServiceInitialUserInfoReq() {
+        EventLogHelper.writeCarUserServiceInitialUserInfoReq(1, 2, 3, 4, 5);
+
+        assertLogMessage("I car_user_svc_initial_user_info_req: [1,2,3,4,5]");
+    }
+
+    @Test
+    public void  testWriteCarUserServiceInitialUserInfoResp() {
+        EventLogHelper.writeCarUserServiceInitialUserInfoResp(1, 2, 3, 4, "string1", "string2");
+
+        assertLogMessage("I car_user_svc_initial_user_info_resp: [1,2,3,4,string1,string2]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSetInitialUser() {
+        EventLogHelper.writeCarUserServiceSetInitialUser(101);
+
+        assertLogMessage("I car_user_svc_set_initial_user: 101");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSetLifecycleListener() {
+        EventLogHelper.writeCarUserServiceSetLifecycleListener(101, "string1");
+
+        assertLogMessage("I car_user_svc_set_lifecycle_listener: [101,string1]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceResetLifecycleListener() {
+        EventLogHelper.writeCarUserServiceResetLifecycleListener(101, "string1");
+
+        assertLogMessage("I car_user_svc_reset_lifecycle_listener: [101,string1]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSwitchUserReq() {
+        EventLogHelper.writeCarUserServiceSwitchUserReq(101, 102);
+
+        assertLogMessage("I car_user_svc_switch_user_req: [101,102]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSwitchUserResp() {
+        EventLogHelper.writeCarUserServiceSwitchUserResp(101, 102, "string");
+
+        assertLogMessage("I car_user_svc_switch_user_resp: [101,102,string]");
+    }
+
+    @Test
+    public void testWriteCarUserServicePostSwitchUserReq() {
+        EventLogHelper.writeCarUserServicePostSwitchUserReq(101, 102);
+
+        assertLogMessage("I car_user_svc_post_switch_user_req: [101,102]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceGetUserAuthReq() {
+        EventLogHelper.writeCarUserServiceGetUserAuthReq(101, 102, 103);
+
+        assertLogMessage("I car_user_svc_get_user_auth_req: [101,102,103]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceGetUserAuthResp() {
+        EventLogHelper.writeCarUserServiceGetUserAuthResp(101);
+
+        assertLogMessage("I car_user_svc_get_user_auth_resp: 101");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSwitchUserUiReq() {
+        EventLogHelper.writeCarUserServiceSwitchUserUiReq(101);
+
+        assertLogMessage("I car_user_svc_switch_user_ui_req: 101");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSwitchUserFromHalReq() {
+        EventLogHelper.writeCarUserServiceSwitchUserFromHalReq(101, 102);
+
+        assertLogMessage("I car_user_svc_switch_user_from_hal_req: [101,102]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSetUserAuthReq() {
+        EventLogHelper.writeCarUserServiceSetUserAuthReq(101, 102, 103);
+
+        assertLogMessage("I car_user_svc_set_user_auth_req: [101,102,103]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceSetUserAuthResp() {
+        EventLogHelper.writeCarUserServiceSetUserAuthResp(101, "string");
+
+        assertLogMessage("I car_user_svc_set_user_auth_resp: [101,string]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceCreateUserReq() {
+        EventLogHelper.writeCarUserServiceCreateUserReq("string1", "string2", 101, 102, 103);
+
+        assertLogMessage("I car_user_svc_create_user_req: [string1,string2,101,102,103]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceCreateUserResp() {
+        EventLogHelper.writeCarUserServiceCreateUserResp(101, 102, "string");
+
+        assertLogMessage("I car_user_svc_create_user_resp: [101,102,string]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceCreateUserUserCreated() {
+        EventLogHelper.writeCarUserServiceCreateUserUserCreated(101, "string1", "string2", 102);
+
+        assertLogMessage("I car_user_svc_create_user_user_created: [101,string1,string2,102]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceCreateUserUserRemoved() {
+        EventLogHelper.writeCarUserServiceCreateUserUserRemoved(101, "string");
+
+        assertLogMessage("I car_user_svc_create_user_user_removed: [101,string]");
+    }
+
+    @Test
+    public void testWriteCarUserServiceRemoveUserReq() {
+        EventLogHelper.writeCarUserServiceRemoveUserReq(101, 102);
+
+        assertLogMessage("I car_user_svc_remove_user_req: [101,102]");
+    }
+
+    private void assertLogMessage(String match) {
+        LogcatHelper.assertLogcatMessage(match, BUFFER, TIMEOUT_MS);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
new file mode 100644
index 0000000..0648f56
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
@@ -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.
+ */
+
+package android.car.cts.builtin.util;
+
+import static org.junit.Assert.fail;
+
+import android.app.UiAutomation;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.io.BufferedReader;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+public final class LogcatHelper {
+
+    private LogcatHelper() {}
+
+    /**
+     * Logcat buffers to search.
+     */
+    public enum Buffer{
+        EVENTS, MAIN, SYSTEM, ALL;
+    }
+
+    /**
+     * Asserts if a message appears in logcat messages within given timeout. All logcat buffers are
+     * searched.
+     *
+     * @param match to find in the logcat messages
+     * @param timeout for waiting the message
+     */
+    public static void assertLogcatMessage(String match, int timeout) {
+        assertLogcatMessage(match, Buffer.ALL, timeout);
+    }
+
+    /**
+     * Asserts if a message appears in logcat messages within given timeout in the given buffer.
+     *
+     * @param match to find in the logcat messages
+     * @param buffer is logcat buffer to search
+     * @param timeout for waiting the message
+     */
+    public static void assertLogcatMessage(String match, Buffer buffer, int timeout) {
+        long startTime = SystemClock.elapsedRealtime();
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor output = automation
+                .executeShellCommand("logcat -b " + buffer.name().toLowerCase());
+        FileDescriptor fd = output.getFileDescriptor();
+        FileInputStream fileInputStream = new FileInputStream(fd);
+        try (BufferedReader bufferedReader = new BufferedReader(
+                new InputStreamReader(fileInputStream))) {
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                if (line.contains(match)) {
+                    return;
+                }
+                if ((SystemClock.elapsedRealtime() - startTime) > timeout) {
+                    fail("match" + match + " was not found, Timeout: " + timeout + " ms");
+                }
+            }
+        } catch (IOException e) {
+            fail("match was not found, IO exception: " + e);
+        }
+    }
+
+    /**
+     * Asserts if a message does not appears in logcat messages within given timeout. If the message
+     * appears, then assertion will fail.
+     *
+     * @param match to find in the logcat messages
+     * @param timeout for waiting the message
+     */
+    public static void assertNoLogcatMessage(String match, int timeout) throws Exception {
+        long startTime = SystemClock.elapsedRealtime();
+        UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        ParcelFileDescriptor output = automation.executeShellCommand("logcat -b all");
+        FileDescriptor fd = output.getFileDescriptor();
+        FileInputStream fileInputStream = new FileInputStream(fd);
+        try (BufferedReader bufferedReader = new BufferedReader(
+                new InputStreamReader(fileInputStream))) {
+            String line;
+            while ((line = bufferedReader.readLine()) != null) {
+                if (line.contains(match)) {
+                    fail("Match was not expected, but found: " + match);
+                }
+                if ((SystemClock.elapsedRealtime() - startTime) > timeout) {
+                    return;
+                }
+            }
+        } catch (IOException e) {
+            fail("match was not found, IO exception: " + e);
+        }
+    }
+
+    /**
+     * Clears all logs.
+     */
+    public static void clearLog() {
+        SystemUtil.runShellCommand("logcat -b all -c");
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java
new file mode 100644
index 0000000..896da38
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/SlogfTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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.builtin.util;
+
+import static android.car.cts.builtin.util.SlogfTest.Level.DEBUG;
+import static android.car.cts.builtin.util.SlogfTest.Level.ERROR;
+import static android.car.cts.builtin.util.SlogfTest.Level.VERBOSE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.builtin.util.Slogf;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public final class SlogfTest {
+    private static final String TAG = SlogfTest.class.getSimpleName();
+    private static final int TIMEOUT_MS = 60_000;
+    // All Slogf would be logged to system buffer.
+    private static final LogcatHelper.Buffer BUFFER = LogcatHelper.Buffer.SYSTEM;
+    // wait time for waiting to make sure msg is not logged. Should not be a high value as tests
+    // waits for this much time.
+    private static final int NOT_LOGGED_WAIT_TIME_MS = 5_000;
+    private static final String LOGCAT_LINE_FORMAT = "%s %s: %s";
+
+    private static final String LOGGED_MSG = "This message should exist in logcat.";
+    private static final String NOT_LOGGED_MSG = "This message should not exist in logcat.";
+    private static final String FORMATTED_MSG = "This message is a format with two args %s and %s.";
+    private static final String EXCEPTION_MSG = "This message should exist in logcat.";
+
+    enum Level {
+        VERBOSE("V"), DEBUG("D"), ERROR("E");
+
+        private String mValue;
+
+        public String getValue() {
+            return mValue;
+        }
+
+        Level(String v) {
+            mValue = v;
+        }
+    }
+
+    @Before
+    public void setup() {
+        setLogLevel(VERBOSE);
+        clearLog();
+    }
+
+    @After
+    public void reset() {
+        setLogLevel(VERBOSE);
+        clearLog();
+    }
+
+    @Test
+    public void testV_msg1() {
+        Slogf.v(TAG, LOGGED_MSG);
+
+        assertLogcatMessage(VERBOSE, LOGGED_MSG);
+    }
+
+    @Test
+    public void testV_msg2() throws Exception {
+        Throwable throwable = new Throwable(EXCEPTION_MSG);
+
+        Slogf.v(TAG, LOGGED_MSG, throwable);
+
+        assertLogcatMessage(VERBOSE, LOGGED_MSG);
+        assertLogcatMessage(VERBOSE, "java.lang.Throwable: " + EXCEPTION_MSG);
+        assertLogcatStackTrace(VERBOSE, throwable);
+
+    }
+
+    @Test
+    public void testV_msg3() {
+        Slogf.v(TAG, FORMATTED_MSG, "input1", "input2");
+
+        assertLogcatMessage(VERBOSE, String.format(FORMATTED_MSG, "input1", "input2"));
+    }
+
+    @Test
+    public void testV_noMsg1() throws Exception {
+        setLogLevel(ERROR);
+
+        Slogf.v(TAG, NOT_LOGGED_MSG);
+
+        assertNoLogcatMessage(VERBOSE, NOT_LOGGED_MSG);
+    }
+
+    @Test
+    public void testV_noMsg2() throws Exception {
+        setLogLevel(ERROR);
+
+        Slogf.v(TAG, FORMATTED_MSG, "input1", "input2");
+
+        assertNoLogcatMessage(VERBOSE, String.format(FORMATTED_MSG, "input1", "input2"));
+    }
+
+    @Test
+    public void testD_msg1() {
+        Slogf.d(TAG, LOGGED_MSG);
+
+        assertLogcatMessage(DEBUG, LOGGED_MSG);
+    }
+
+    @Test
+    public void testD_msg2() {
+        Throwable throwable = new Throwable(EXCEPTION_MSG);
+        Slogf.d(TAG, LOGGED_MSG, throwable);
+
+        assertLogcatMessage(DEBUG, LOGGED_MSG);
+        assertLogcatMessage(DEBUG, "java.lang.Throwable: " + EXCEPTION_MSG);
+        assertLogcatStackTrace(DEBUG, throwable);
+    }
+
+    @Test
+    public void testD_msg3() {
+        Slogf.d(TAG, FORMATTED_MSG, "input1", "input2");
+
+        assertLogcatMessage(DEBUG, String.format(FORMATTED_MSG, "input1", "input2"));
+    }
+
+    @Test
+    public void testD_noMsg1() throws Exception {
+        setLogLevel(ERROR);
+
+        Slogf.d(TAG, NOT_LOGGED_MSG);
+
+        assertNoLogcatMessage(DEBUG, NOT_LOGGED_MSG);
+    }
+
+    @Test
+    public void testD_noMsg2() throws Exception {
+        setLogLevel(ERROR);
+
+        Slogf.d(TAG, FORMATTED_MSG, "input1", "input2");
+
+        assertNoLogcatMessage(DEBUG, String.format(FORMATTED_MSG, "input1", "input2"));
+    }
+
+    @Test
+    public void testIsLoggableTrue() throws Exception {
+        setLogLevel(VERBOSE);
+
+        assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isTrue();
+    }
+
+    @Test
+    public void testIsLoggableFalse() throws Exception {
+        setLogLevel(ERROR);
+
+        assertThat(Slogf.isLoggable(TAG, Log.VERBOSE)).isFalse();
+    }
+
+    private void clearLog() {
+        LogcatHelper.clearLog();
+    }
+
+    private void setLogLevel(Level level) {
+        SystemUtil.runShellCommand("setprop log.tag.SlogfTest " + level.getValue());
+    }
+
+    private void assertNoLogcatMessage(Level level, String msg) throws Exception {
+        String match = String.format(LOGCAT_LINE_FORMAT, level.getValue(), TAG, msg);
+        LogcatHelper.assertNoLogcatMessage(match, NOT_LOGGED_WAIT_TIME_MS);
+    }
+
+    private void assertLogcatMessage(Level level, String msg) {
+        String match = String.format(LOGCAT_LINE_FORMAT, level.getValue(), TAG, msg);
+        LogcatHelper.assertLogcatMessage(match, BUFFER, TIMEOUT_MS);
+    }
+
+    private void assertLogcatStackTrace(Level level, Throwable throwable) {
+        StackTraceElement[] elements = throwable.getStackTrace();
+        for (int i = 0; i < elements.length; i++) {
+            assertLogcatMessage(level, "\tat " + elements[i]);
+        }
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java
new file mode 100644
index 0000000..92393d3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimeUtilsTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.builtin.util;
+
+import android.car.builtin.util.TimeUtils;
+
+import org.junit.Test;
+
+import java.io.PrintWriter;
+
+public final class TimeUtilsTest {
+    private PrintWriter mWriter = new PrintWriter(System.out);
+
+    private static final int TIMEOUT_MS = 60_000;
+
+    @Test
+    public void testDumpTime() {
+        TimeUtils.dumpTime(mWriter, 179);
+        mWriter.flush();
+
+        // Time utils change long into date-time format.
+        LogcatHelper.assertLogcatMessage("System.out: 1970-01-01 00:00:00.179", TIMEOUT_MS);
+    }
+
+    @Test
+    public void testFormatDuration() {
+        TimeUtils.formatDuration(789, mWriter);
+        mWriter.flush();
+
+        // Time utils change long into human readable text.
+        LogcatHelper.assertLogcatMessage("System.out: +789ms", TIMEOUT_MS);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java
new file mode 100644
index 0000000..0fec391
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/TimingsTraceLogTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.builtin.util;
+
+import static android.car.cts.builtin.util.LogcatHelper.assertLogcatMessage;
+
+import android.car.builtin.os.TraceHelper;
+import android.car.builtin.util.TimingsTraceLog;
+
+import org.junit.Test;
+
+public final class TimingsTraceLogTest {
+
+    private static final String TAG = TimingsTraceLogTest.class.getSimpleName();
+    private static final int TIMEOUT_MS = 60_000;
+
+    @Test
+    public void testTimingsTraceLog() {
+        TimingsTraceLog timingsTraceLog =
+                new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
+        timingsTraceLog.traceBegin("testTimingsTraceLog");
+        timingsTraceLog.traceEnd();
+
+        assertLogcatMessage("TimingsTraceLogTest: testTimingsTraceLog took to complete",
+                TIMEOUT_MS);
+    }
+
+    @Test
+    public void testTimingsTraceLogDuration() {
+        TimingsTraceLog timingsTraceLog =
+                new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
+        timingsTraceLog.logDuration("testTimingsTraceLogDuration", 159);
+
+        assertLogcatMessage(
+                "TimingsTraceLogTest: testTimingsTraceLogDuration took to complete: 159ms",
+                TIMEOUT_MS);
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.java
new file mode 100644
index 0000000..7fb1da3
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/util/ValidationHelperTest.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 android.car.cts.builtin.util;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.car.builtin.util.ValidationHelper;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public final class ValidationHelperTest {
+    // constants from android.os.UserHandle
+    private static final int USER_NULL = -10000;
+    private static final int USER_CURRENT_OR_SELF = -3;
+    private static final int USER_CURRENT = -2;
+    private static final int USER_ALL = -1;
+    private static final int USER_SYSTEM = 0;
+    private static final int PER_USER_RANGE = 100000;
+
+    @Test
+    public void testUserIdValidation() {
+        // setup
+        int maxUserId = Integer.MAX_VALUE / PER_USER_RANGE;
+        int invalidUserId = maxUserId + 1;
+
+        // assert pre-defined user ids
+        assertTrue(ValidationHelper.isUserIdValid(USER_NULL));
+        assertTrue(ValidationHelper.isUserIdValid(USER_CURRENT_OR_SELF));
+        assertTrue(ValidationHelper.isUserIdValid(USER_CURRENT));
+        assertTrue(ValidationHelper.isUserIdValid(USER_ALL));
+        assertTrue(ValidationHelper.isUserIdValid(USER_SYSTEM));
+        assertTrue(ValidationHelper.isUserIdValid(maxUserId));
+
+        // assert dynamical user ids
+        assertTrue(ValidationHelper.isUserIdValid(UserHandle.myUserId()));
+
+        // assert boundary conditions
+        assertFalse(ValidationHelper.isUserIdValid(invalidUserId));
+    }
+
+    @Test
+    public void testAppIdValidation() {
+        // setup
+        int outOfRangeAppId = PER_USER_RANGE + 1;
+
+        // assert pre-defined app ids
+        assertTrue(ValidationHelper.isAppIdValid(Process.FIRST_APPLICATION_UID));
+        assertTrue(ValidationHelper.isAppIdValid(Process.LAST_APPLICATION_UID));
+        assertTrue(ValidationHelper.isAppIdValid(Process.ROOT_UID));
+        assertTrue(ValidationHelper.isAppIdValid(Process.SYSTEM_UID));
+        assertTrue(ValidationHelper.isAppIdValid(Process.SHELL_UID));
+
+        // assert dynamical app ids
+        assertTrue(ValidationHelper.isAppIdValid(Process.myUid() % PER_USER_RANGE));
+        assertTrue(ValidationHelper.isAppIdValid(Process.FIRST_APPLICATION_UID + 1));
+        assertTrue(ValidationHelper.isAppIdValid(Process.LAST_APPLICATION_UID - 1));
+
+        // assert boundary conditions
+        assertFalse(ValidationHelper.isAppIdValid(Process.INVALID_UID));
+        assertFalse(ValidationHelper.isAppIdValid(outOfRangeAppId));
+    }
+}
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java
new file mode 100644
index 0000000..5ac7527
--- /dev/null
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/view/DisplayHelperTest.java
@@ -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.
+ */
+
+package android.car.cts.builtin.view;
+
+import static android.car.cts.builtin.app.DisplayUtils.VirtualDisplaySession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.car.builtin.view.DisplayHelper;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.display.DisplayManager;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+public final class DisplayHelperTest {
+
+    private static final String TAG = DisplayHelperTest.class.getSimpleName();
+
+    // the constant comes from com.android.server.display.LocalDisplayAdapter.UNIQUE_ID_PREFIX
+    private static final String DISPLAY_ID_PREFIX_LOCAL = "local:";
+    // the constant comes from com.android.server.display.VirtualDisplayAdapter.UNIQUE_ID_PREFIX
+    private static final String DISPLAY_ID_PREFIX_VIRTUAL = "virtual:";
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testLocalDisplayPhysicalPort() throws Exception {
+        // setup
+        ArrayList<Display> allLocalDisplays = getAllLocalDisplays();
+
+        // assert that there is at least one local display
+        assertThat(allLocalDisplays.size()).isGreaterThan(0);
+
+        // execution and assertion
+        for (Display d : allLocalDisplays) {
+            int physicalPort = DisplayHelper.getPhysicalPort(d);
+            Log.d(TAG, "Display Physical Port: " + physicalPort);
+            assertThat(physicalPort).isNotEqualTo(DisplayHelper.INVALID_PORT);
+
+            String uniqueId = DisplayHelper.getUniqueId(d);
+            assertThat(physicalPort).isEqualTo(getPhysicalPortFromId(uniqueId));
+        }
+    }
+
+    @Test
+    public void testVirtualDisplayPhysicalPort() throws Exception {
+        // check the assumption
+        String requiredFeature = PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(requiredFeature));
+
+        try (VirtualDisplaySession session = new VirtualDisplaySession()) {
+            Display vDisplay =
+                    session.createDisplayWithDefaultDisplayMetricsAndWait(mContext, true);
+            assertThat(DisplayHelper.getPhysicalPort(vDisplay))
+                    .isEqualTo(DisplayHelper.INVALID_PORT);
+        }
+    }
+
+    private ArrayList<Display> getAllLocalDisplays() {
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        Display[] allDisplays = displayManager.getDisplays();
+
+        ArrayList<Display> localDisplays = new ArrayList<>();
+        for (Display d : allDisplays) {
+            if (isDisplayLocal(d)) {
+                localDisplays.add(d);
+            }
+        }
+
+        return localDisplays;
+    }
+
+    private boolean isDisplayLocal(Display display) {
+        String uniqueId = DisplayHelper.getUniqueId(display);
+        return uniqueId.startsWith(DISPLAY_ID_PREFIX_LOCAL);
+    }
+
+    private int getPhysicalPortFromId(String displayUniqueId) throws Exception {
+        if (displayUniqueId == null) {
+            throw new IllegalArgumentException("null displayId string");
+        }
+
+        int startIndex = DISPLAY_ID_PREFIX_LOCAL.length();
+        long physicalDisplayId = Long.parseLong(displayUniqueId.substring(startIndex));
+        return (int) (physicalDisplayId & 0xFF);
+    }
+}
diff --git a/tests/tests/companion/Android.bp b/tests/tests/companion/Android.bp
new file mode 100644
index 0000000..22a45e8
--- /dev/null
+++ b/tests/tests/companion/Android.bp
@@ -0,0 +1,111 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_library {
+    name: "cts-companion-common",
+    srcs: [
+        "common/src/**/*.kt",
+    ],
+    manifest: "common/AndroidManifest.xml",
+
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "junit",
+        "kotlin-test",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+}
+
+android_test {
+    name: "CtsCompanionDeviceManagerCoreTestCases",
+    srcs: [
+        "core/src/**/*.kt",
+    ],
+    manifest: "core/AndroidManifest.xml",
+    test_config: "core/AndroidTest.xml",
+
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "cts-companion-common",
+        "ctstestrunner-axt",
+        "junit",
+        "kotlin-test",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+}
+
+android_test {
+    name: "CtsCompanionDeviceManagerUiAutomationTestCases",
+    srcs: [
+        "uiautomation/src/**/*.kt",
+    ],
+    manifest: "uiautomation/AndroidManifest.xml",
+    test_config: "uiautomation/AndroidTest.xml",
+
+    platform_apis: true,
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator",
+        "compatibility-device-util-axt",
+        "cts-companion-common",
+        "ctstestrunner-axt",
+        "junit",
+        "kotlin-test",
+    ],
+    libs: [
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    // TODO(b/211264986) Change both SDK versions to T (33) once it's formally defined.
+    min_sdk_version: "current",
+    target_sdk_version: "current",
+}
diff --git a/tests/tests/companion/OWNERS b/tests/tests/companion/OWNERS
new file mode 100644
index 0000000..23e30ab
--- /dev/null
+++ b/tests/tests/companion/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 708992
+evanxinchen@google.com
+ewol@google.com
+guojing@google.com
+sergeynv@google.com
diff --git a/tests/tests/companion/README.md b/tests/tests/companion/README.md
new file mode 100644
index 0000000..560f8f7
--- /dev/null
+++ b/tests/tests/companion/README.md
@@ -0,0 +1,14 @@
+CTS tests for `CompanionDeviceManager` are split into 2 modules:
+`CtsCompanionDeviceManagerCoreTestCases` (a.k.a. "Core Tests") and
+`CtsCompanionDeviceManagerUiAutomationTestCases` (a.k.a. "UiAutomation Tests").
+
+The core difference between the two test modules is that `CtsCompanionDeviceManager_Core_TestCases`
+does NOT use `UiAutomation` which makes it:
+- faster
+- suitable for to run on NFFs
+- less prone to flakiness
+- better suitable to run in `PRESUBMIT`.
+
+`CtsCompanionDeviceManager_UiAutomation_TestCases`, on the other hand, uses `UiAutomation` in order
+to test CDM flows end-to-end and is (at least for now) designed to run only on the mobile
+form-factor and requires a discoverable BT device nearby.
diff --git a/tests/tests/companion/common/AndroidManifest.xml b/tests/tests/companion/common/AndroidManifest.xml
new file mode 100644
index 0000000..6a9a3bc
--- /dev/null
+++ b/tests/tests/companion/common/AndroidManifest.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.companion.cts.common">
+
+    <uses-feature android:name="android.software.companion_device_setup" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+
+        <service
+                android:name=".PrimaryCompanionService"
+                android:exported="true"
+                android:label="Primary Companion Service"
+                android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
+            <intent-filter>
+                <action android:name="android.companion.CompanionDeviceService" />
+            </intent-filter>
+
+            <meta-data
+                    android:name="android.companion.primary"
+                    android:value="true" />
+        </service>
+
+        <service
+                android:name=".SecondaryCompanionService"
+                android:exported="true"
+                android:label="Secondary Companion Service"
+                android:permission="android.permission.BIND_COMPANION_DEVICE_SERVICE">
+            <intent-filter>
+                <action android:name="android.companion.CompanionDeviceService" />
+            </intent-filter>
+        </service>
+
+        <activity
+                android:name=".CompanionActivity"
+                android:exported="false"
+                android:label="Companion Activity"
+                android:launchMode="singleInstance"
+                android:excludeFromRecents="true"/>
+    </application>
+
+</manifest>
+
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt b/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt
new file mode 100644
index 0000000..31fe4d6
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/AppHelper.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.companion.cts.common
+
+import android.app.Instrumentation
+import android.net.MacAddress
+import java.lang.UnsupportedOperationException
+
+/** Utility class for interacting with applications via Shell */
+class AppHelper(
+    private val instrumentation: Instrumentation,
+    val userId: Int,
+    val packageName: String,
+    private val apkPath: String? = null
+) {
+    fun associate(macAddress: MacAddress) =
+            runShellCommand("cmd companiondevice associate $userId $packageName $macAddress")
+
+    fun disassociate(macAddress: MacAddress) =
+            runShellCommand("cmd companiondevice disassociate $userId $packageName $macAddress")
+
+    fun isInstalled(): Boolean =
+            runShellCommand("pm list packages --user $userId $packageName").isNotBlank()
+
+    fun install() = apkPath?.let { runShellCommand("pm install --user $userId $apkPath") }
+            ?: throw UnsupportedOperationException("APK path is not provided.")
+
+    fun uninstall() = runShellCommand("pm uninstall --user $userId $packageName")
+
+    fun clearData() = runShellCommand("pm clear --user $userId $packageName")
+
+    fun addToHoldersOfRole(role: String) =
+            runShellCommand("cmd role add-role-holder --user $userId $role $packageName")
+
+    fun removeFromHoldersOfRole(role: String) =
+            runShellCommand("cmd role remove-role-holder --user $userId $role $packageName")
+
+    fun withRole(role: String, block: () -> Unit) {
+        addToHoldersOfRole(role)
+        try {
+            block()
+        } finally {
+            removeFromHoldersOfRole(role)
+        }
+    }
+
+    private fun runShellCommand(cmd: String) = instrumentation.runShellCommand(cmd)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt b/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt
new file mode 100644
index 0000000..833e718
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/CompanionActivity.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.companion.cts.common
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.IntentSender
+import android.os.Bundle
+import android.util.Log
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * An [Activity] for launching confirmation UI via an [android.content.IntentSender.sendIntent] and
+ * receiving result in [onActivityResult].
+ */
+class CompanionActivity : Activity() {
+    private var result: Pair<Int, Intent?>? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        Log.d(TAG, "$this.onCreate()")
+        super.onCreate(savedInstanceState)
+        unsafeInstance = this
+    }
+
+    override fun onDestroy() {
+        Log.d(TAG, "$this.onDestroy()")
+        unsafeInstance = null
+        super.onDestroy()
+    }
+
+    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+        Log.i(TAG, "onActivityResult() code=${resultCode.codeToString()}, " +
+                "data=${intent.extras}")
+        result = resultCode to data
+    }
+
+    companion object {
+        private var unsafeInstance: CompanionActivity? = null
+        val instance: CompanionActivity
+            get() = unsafeInstance ?: error("There is no CompanionActivity")
+
+        fun launchAndWait(context: Context) {
+            val intent = Intent(context, CompanionActivity::class.java)
+            intent.addFlags(FLAG_ACTIVITY_NEW_TASK)
+            context.startActivity(intent)
+
+            waitForResult(timeout = 3.seconds, interval = 100.milliseconds) {
+                unsafeInstance?.takeIf { it.isResumed }
+            } ?: error("CompanionActivity has not appeared")
+        }
+
+        fun startIntentSender(intentSender: IntentSender) =
+                instance.startIntentSenderForResult(intentSender, 0, null, 0, 0, 0)
+
+        fun waitForActivityResult() =
+                waitForResult(timeout = 1.seconds, interval = 100.milliseconds) { instance.result }
+                    ?: error("onActivityResult() has not been invoked")
+
+        fun finish() = instance.finish()
+
+        fun safeFinish() = unsafeInstance?.finish()
+
+        fun waitUntilGone() = waitFor { unsafeInstance == null }
+    }
+}
+
+private fun Int.codeToString() = when (this) {
+    Activity.RESULT_OK -> "RESULT_OK"
+    Activity.RESULT_CANCELED -> "RESULT_CANCELED"
+    else -> "Unknown"
+} + "($this)"
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt b/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt
new file mode 100644
index 0000000..4c16478
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/CompanionService.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceService
+import android.content.Intent
+import android.os.Handler
+import android.util.Log
+import java.util.Collections.synchronizedMap
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+sealed class CompanionService<T : CompanionService<T>>(
+    private val instanceHolder: InstanceHolder<T>
+) : CompanionDeviceService() {
+    @Volatile var isBound: Boolean = false
+        private set(isBound) {
+            Log.d(TAG, "$this.isBound=$isBound")
+            if (!isBound && !connectedDevices.isEmpty())
+                error("Unbinding while there are connected devices")
+            field = isBound
+        }
+
+    val connectedDevices: Collection<AssociationInfo>
+        get() = _connectedDevices.values
+
+    val associationIdsForConnectedDevices: Collection<Int>
+        get() = _connectedDevices.keys
+
+    private val _connectedDevices: MutableMap<Int, AssociationInfo> =
+            synchronizedMap(mutableMapOf())
+
+    override fun onCreate() {
+        Log.d(TAG, "$this.onCreate()")
+        super.onCreate()
+        instanceHolder.instance = this as T
+    }
+
+    override fun onBindCompanionDeviceService(intent: Intent) {
+        Log.d(TAG, "$this.onBindCompanionDeviceService()")
+        isBound = true
+    }
+
+    override fun onDeviceAppeared(associationInfo: AssociationInfo) {
+        Log.d(TAG, "$this.onDevice_Appeared(), association=$associationInfo")
+        _connectedDevices[associationInfo.id] = associationInfo
+        super.onDeviceAppeared(associationInfo)
+    }
+
+    override fun onDeviceDisappeared(associationInfo: AssociationInfo) {
+        Log.d(TAG, "$this.onDevice_Disappeared(), association=$associationInfo")
+        _connectedDevices.remove(associationInfo.id)
+                ?: error("onDeviceAppeared() has not been called for association with id " +
+                        "${associationInfo.id}")
+
+        super.onDeviceDisappeared(associationInfo)
+    }
+
+    // For now, we need to "post" a Runnable that sets isBound to false to the Main Thread's
+    // Handler, because this may be called between invocations of
+    // CompanionDeviceService.Stub.onDeviceAppeared() and the "real"
+    // CompanionDeviceService.onDeviceAppeared(), which would cause an error() in isBound setter.
+    override fun onUnbind(intent: Intent?) = super.onUnbind(intent)
+            .also {
+                Log.d(TAG, "$this.onUnbind()")
+                Handler.getMain().post { isBound = false }
+            }
+
+    override fun onDestroy() {
+        Log.d(TAG, "$this.onDestroy()")
+        instanceHolder.instance = null
+        super.onDestroy()
+    }
+}
+
+sealed class InstanceHolder<T : CompanionService<T>> {
+    // Need synchronization, because the setter will be called from the Main thread, while the
+    // getter is expected to be called mostly from the instrumentation thread.
+    var instance: T? = null
+        @Synchronized internal set
+        @Synchronized get
+
+    val isBound: Boolean
+        get() = instance?.isBound ?: false
+
+    val connectedDevices: Collection<AssociationInfo>
+        get() = instance?.connectedDevices ?: emptySet()
+
+    val associationIdsForConnectedDevices: Collection<Int>
+        get() = instance?.associationIdsForConnectedDevices ?: emptySet()
+
+    fun waitForBind(timeout: Duration = 1.seconds) {
+        if (!waitFor(timeout) { isBound })
+            throw AssertionError("Service hasn't been bound")
+    }
+
+    fun waitForUnbind(timeout: Duration) {
+        if (!waitFor(timeout) { !isBound })
+            throw AssertionError("Service hasn't been unbound")
+    }
+
+    fun waitAssociationToAppear(associationId: Int, timeout: Duration = 1.seconds) {
+        val appeared = waitFor(timeout) {
+            associationIdsForConnectedDevices.contains(associationId)
+        }
+        if (!appeared) throw AssertionError("""Association with $associationId hasn't "appeared"""")
+    }
+
+    fun waitAssociationToDisappear(associationId: Int, timeout: Duration = 1.seconds) {
+        val gone = waitFor(timeout) {
+            !associationIdsForConnectedDevices.contains(associationId)
+        }
+        if (!gone) throw AssertionError("""Association with $associationId hasn't "disappeared"""")
+    }
+}
+
+class PrimaryCompanionService : CompanionService<PrimaryCompanionService>(Companion) {
+    companion object : InstanceHolder<PrimaryCompanionService>()
+}
+
+class SecondaryCompanionService : CompanionService<SecondaryCompanionService>(Companion) {
+    companion object : InstanceHolder<SecondaryCompanionService>()
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt b/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt
new file mode 100644
index 0000000..c7471d1
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/Constants.kt
@@ -0,0 +1,53 @@
+package android.companion.cts.common
+
+import android.Manifest
+import android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+import android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.net.MacAddress
+import android.os.Handler
+import android.os.HandlerThread
+import java.util.concurrent.Executor
+
+/** Set of all supported CDM Device Profiles. */
+val DEVICE_PROFILES = setOf(
+        DEVICE_PROFILE_WATCH,
+        DEVICE_PROFILE_APP_STREAMING,
+        DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+)
+
+val DEVICE_PROFILE_TO_NAME = mapOf(
+        DEVICE_PROFILE_WATCH to "WATCH",
+        DEVICE_PROFILE_APP_STREAMING to "APP_STREAMING",
+        DEVICE_PROFILE_AUTOMOTIVE_PROJECTION to "AUTOMOTIVE_PROJECTION"
+)
+
+val DEVICE_PROFILE_TO_PERMISSION = mapOf(
+        DEVICE_PROFILE_WATCH to Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH,
+        DEVICE_PROFILE_APP_STREAMING to
+                Manifest.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING,
+        DEVICE_PROFILE_AUTOMOTIVE_PROJECTION to
+                Manifest.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION
+)
+
+val MAC_ADDRESS_A = MacAddress.fromString("00:00:00:00:00:AA")
+val MAC_ADDRESS_B = MacAddress.fromString("00:00:00:00:00:BB")
+val MAC_ADDRESS_C = MacAddress.fromString("00:00:00:00:00:CC")
+
+const val DEVICE_DISPLAY_NAME_A = "Device A"
+const val DEVICE_DISPLAY_NAME_B = "Device B"
+
+val SIMPLE_EXECUTOR: Executor by lazy { Executor { it.run() } }
+
+val MAIN_THREAD_EXECUTOR: Executor by lazy {
+    Executor {
+        with(Handler.getMain()) { post(it) }
+    }
+}
+
+val BACKGROUND_THREAD_EXECUTOR: Executor by lazy {
+    with(HandlerThread("CdmTestBackgroundThread")) {
+        start()
+        Executor { threadHandler.post(it) }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt b/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt
new file mode 100644
index 0000000..4f7ed66
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/InvocationTracker.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+interface InvocationTracker<T> {
+    val invocations: List<T>
+
+    /**
+     * Await invocations of this callback by the given [actions].
+     */
+    fun assertInvokedByActions(
+        timeout: Duration = 1.seconds,
+        minOccurrences: Int = 1,
+        actions: () -> Unit
+    ) {
+        require(minOccurrences > 0) {
+            "Must expect at least one callback occurrence. (Given $minOccurrences)"
+        }
+        val expectedInvocationCount = invocations.size + minOccurrences
+        actions()
+        if (!waitFor(timeout, interval = 100.milliseconds) {
+                invocations.size >= expectedInvocationCount
+        }) {
+            throw AssertionError(
+                "Callback was invoked ${invocations.size} times after $timeout ms! " +
+                        "Expected at least $minOccurrences times."
+            )
+        }
+    }
+
+    fun clearRecordedInvocations()
+
+    fun recordInvocation(invocation: T)
+}
+
+internal class InvocationContainer<T> : InvocationTracker<T> {
+    private val _invocations: MutableList<T> = mutableListOf()
+    override val invocations: List<T>
+        @Synchronized
+        get() = _invocations
+
+    @Synchronized
+    override fun clearRecordedInvocations() = _invocations.clear()
+
+    @Synchronized
+    override fun recordInvocation(invocation: T) {
+        _invocations.add(invocation)
+    }
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt
new file mode 100644
index 0000000..52a4610
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCallback.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.content.IntentSender
+import android.util.Log
+
+class RecordingCallback
+private constructor(container: InvocationContainer<CallbackInvocation>) :
+    CompanionDeviceManager.Callback(),
+    InvocationTracker<RecordingCallback.CallbackInvocation> by container {
+
+    constructor() : this(InvocationContainer())
+
+    override fun onDeviceFound(intentSender: IntentSender) =
+        logAndRecordInvocation(OnDeviceFound(intentSender))
+
+    override fun onAssociationPending(intentSender: IntentSender) =
+        logAndRecordInvocation(OnAssociationPending(intentSender))
+
+    override fun onAssociationCreated(associationInfo: AssociationInfo) =
+        logAndRecordInvocation(OnAssociationCreated(associationInfo))
+
+    override fun onFailure(error: CharSequence?) = logAndRecordInvocation(OnFailure(error))
+
+    private fun logAndRecordInvocation(invocation: CallbackInvocation) {
+        Log.d(TAG, "Callback: $invocation")
+        recordInvocation(invocation)
+    }
+
+    sealed interface CallbackInvocation
+    data class OnDeviceFound(val intentSender: IntentSender) : CallbackInvocation
+    data class OnAssociationPending(val intentSender: IntentSender) : CallbackInvocation
+    data class OnAssociationCreated(val associationInfo: AssociationInfo) : CallbackInvocation
+    data class OnFailure(val error: CharSequence?) : CallbackInvocation
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt
new file mode 100644
index 0000000..7ba5552
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingCdmEventObserver.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.RecordingCallback.CallbackInvocation
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.RecordingCallback.OnDeviceFound
+import android.companion.cts.common.RecordingCallback.OnFailure
+import android.content.IntentSender
+
+/**
+ * This class is a combination of a
+ * [android.companion.CompanionDeviceManager.OnAssociationsChangedListener] and a
+ * [android.companion.CompanionDeviceManager.Callback].
+ *
+ * It can simultaneously serve as both an association change listener and a CDM callback,
+ * enabling it to make assertions on the order of all externally observable CDM events.
+ */
+class RecordingCdmEventObserver
+private constructor(container: InvocationContainer<CdmEvent>) :
+    CompanionDeviceManager.Callback(),
+    CompanionDeviceManager.OnAssociationsChangedListener,
+    InvocationTracker<RecordingCdmEventObserver.CdmEvent> by container {
+
+    constructor() : this(InvocationContainer())
+
+    // association change listener behavior
+
+    override fun onAssociationsChanged(associations: List<AssociationInfo>) =
+        recordInvocation(AssociationChange(associations))
+
+    // CDM callback behavior
+
+    override fun onDeviceFound(intentSender: IntentSender) =
+        recordInvocation(CdmCallback(OnDeviceFound(intentSender)))
+
+    override fun onAssociationPending(intentSender: IntentSender) =
+        recordInvocation(CdmCallback(OnAssociationPending(intentSender)))
+
+    override fun onAssociationCreated(associationInfo: AssociationInfo) =
+        recordInvocation(CdmCallback(OnAssociationCreated(associationInfo)))
+
+    override fun onFailure(error: CharSequence?) =
+        recordInvocation(CdmCallback(OnFailure(error)))
+
+    sealed interface CdmEvent
+    data class AssociationChange(val associations: List<AssociationInfo>) : CdmEvent
+    data class CdmCallback(val invocation: CallbackInvocation) : CdmEvent
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt b/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt
new file mode 100644
index 0000000..c024a79
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/RecordingOnAssociationsChangedListener.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.companion.cts.common
+
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+
+class RecordingOnAssociationsChangedListener
+private constructor(container: InvocationContainer<List<AssociationInfo>>) :
+    CompanionDeviceManager.OnAssociationsChangedListener,
+    InvocationTracker<List<AssociationInfo>> by container {
+
+    constructor() : this(InvocationContainer())
+
+    override fun onAssociationsChanged(associations: List<AssociationInfo>) =
+        recordInvocation(associations)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt b/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt
new file mode 100644
index 0000000..c0370bf
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/Repeat.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.companion.cts.common
+
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Repeats the execution of the annotated test method the given number of times (10 by default).
+ *
+ * Note that a [RepeatRule] must be present in the class for the annotation to take effect.
+ *
+ * For an example, see the following code:
+ * ```
+ * @get:Rule
+ * val repeatRule = RepeatRule()
+ *
+ * @Test
+ * @Repeat(5)
+ * fun myTest() {
+ *   ...
+ * }
+ * ```
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.FUNCTION)
+annotation class Repeat(val times: Int = 10)
+
+/**
+ * Use this rule together with the [Repeat] annotation to automatically
+ * repeat the execution of annotated tests.
+ */
+class RepeatRule : TestRule {
+    override fun apply(base: Statement, description: Description): Statement =
+        description.getAnnotation(Repeat::class.java)?.let { repeat ->
+            object : Statement() {
+                @Throws(Throwable::class)
+                override fun evaluate() = repeat(repeat.times) { base.evaluate() }
+            }
+        } ?: base
+}
diff --git a/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt b/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt
new file mode 100644
index 0000000..23aa32e
--- /dev/null
+++ b/tests/tests/companion/common/src/android/companion/cts/common/TestBase.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.companion.cts.common
+
+import android.annotation.CallSuper
+import android.app.Instrumentation
+import android.app.UiAutomation
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.MacAddress
+import android.os.SystemClock.sleep
+import android.os.SystemClock.uptimeMillis
+import android.util.Log
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.AssumptionViolatedException
+import org.junit.Before
+import java.io.IOException
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertTrue
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * A base class for CompanionDeviceManager [Tests][org.junit.Test] to extend.
+ */
+abstract class TestBase {
+    protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    protected val uiAutomation: UiAutomation = instrumentation.uiAutomation
+
+    protected val context: Context = instrumentation.context
+    protected val userId = context.userId
+    private val targetPackageName = instrumentation.targetContext.packageName
+
+    protected val targetApp = AppHelper(instrumentation, userId, targetPackageName)
+
+    protected val pm: PackageManager by lazy { context.packageManager!! }
+    private val hasCompanionDeviceSetupFeature by lazy {
+        pm.hasSystemFeature(PackageManager.FEATURE_COMPANION_DEVICE_SETUP)
+    }
+
+    protected val cdm: CompanionDeviceManager by lazy {
+        context.getSystemService(CompanionDeviceManager::class.java)!!
+    }
+
+    @Before
+    fun base_setUp() {
+        assumeTrue(hasCompanionDeviceSetupFeature)
+
+        // Remove all existing associations (for the user).
+        assertEmpty(withShellPermissionIdentity {
+            cdm.disassociateAll()
+            cdm.allAssociations
+        })
+
+        // Make sure CompanionDeviceServices are not bound.
+        assertFalse(PrimaryCompanionService.isBound)
+        assertFalse(SecondaryCompanionService.isBound)
+
+        setUp()
+    }
+
+    @After
+    fun base_tearDown() {
+        if (!hasCompanionDeviceSetupFeature) return
+
+        tearDown()
+
+        // Remove all existing associations (for the user).
+        withShellPermissionIdentity { cdm.disassociateAll() }
+    }
+
+    @CallSuper
+    protected open fun setUp() {}
+
+    @CallSuper
+    protected open fun tearDown() {}
+
+    protected fun <T> withShellPermissionIdentity(
+        vararg permissions: String,
+        block: () -> T
+    ): T {
+        if (permissions.isNotEmpty()) {
+            uiAutomation.adoptShellPermissionIdentity(*permissions)
+        } else {
+            uiAutomation.adoptShellPermissionIdentity()
+        }
+
+        try {
+            return block()
+        } finally {
+            uiAutomation.dropShellPermissionIdentity()
+        }
+    }
+
+    private fun CompanionDeviceManager.disassociateAll() =
+            allAssociations.forEach { disassociate(it.id) }
+}
+
+const val TAG = "CtsCompanionDeviceManagerTestCases"
+
+fun <T> assumeThat(message: String, obj: T, assumption: (T) -> Boolean) {
+    if (!assumption(obj)) throw AssumptionViolatedException(message)
+}
+
+fun <T> assertEmpty(list: Collection<T>) = assertTrue("Collection is not empty") { list.isEmpty() }
+
+fun assertAssociations(
+    actual: List<AssociationInfo>,
+    expected: Set<Pair<String, MacAddress?>>
+) = assertEquals(actual = actual.map { it.packageName to it.deviceMacAddress }.toSet(),
+        expected = expected)
+
+/**
+ * @return whether the condition was met before time ran out.
+ */
+fun waitFor(
+    timeout: Duration = 10.seconds,
+    interval: Duration = 1.seconds,
+    condition: () -> Boolean
+): Boolean {
+    val startTime = uptimeMillis()
+    while (!condition()) {
+        if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return false
+        sleep(interval.inWholeMilliseconds)
+    }
+    return true
+}
+
+fun <R> waitForResult(
+    timeout: Duration = 10.seconds,
+    interval: Duration = 1.seconds,
+    block: () -> R
+): R? {
+    val startTime = uptimeMillis()
+    while (true) {
+        val result: R = block()
+        if (result != null) return result
+        sleep(interval.inWholeMilliseconds)
+        if (uptimeMillis() - startTime > timeout.inWholeMilliseconds) return null
+    }
+}
+
+fun Instrumentation.runShellCommand(cmd: String): String {
+    Log.i(TAG, "Running shell command: '$cmd'")
+    try {
+        val out = SystemUtil.runShellCommand(this, cmd)
+        Log.i(TAG, "Out:\n$out")
+        return out
+    } catch (e: IOException) {
+        Log.e(TAG, "Error running shell command: $cmd")
+        throw e
+    }
+}
+
+fun Instrumentation.setSystemProp(name: String, value: String) =
+        runShellCommand("setprop $name $value")
\ No newline at end of file
diff --git a/tests/tests/companion/core/AndroidManifest.xml b/tests/tests/companion/core/AndroidManifest.xml
new file mode 100644
index 0000000..048558a
--- /dev/null
+++ b/tests/tests/companion/core/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.companion.cts.core">
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.companion.cts.core"
+        android:label="CompanionDeviceManager Core CTS tests">
+
+        <meta-data
+            android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/companion/core/AndroidTest.xml b/tests/tests/companion/core/AndroidTest.xml
new file mode 100644
index 0000000..21d3dd63
--- /dev/null
+++ b/tests/tests/companion/core/AndroidTest.xml
@@ -0,0 +1,48 @@
+<?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="Configuration for Core CTS tests for CompanionDeviceManager">
+    <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" />
+
+    <option name="not-shardable" value="true" />
+    <option name="test-suite-tag" value="cts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCompanionDeviceManagerCoreTestCases.apk" />
+        <option name="test-file-name" value="CtsCompanionTestApp.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.companion.cts.core" />
+        <option name="runtime-hint" value="1s" />
+    </test>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <!-- Create a temporary directory for test APKs.  -->
+        <option name="run-command" value="mkdir -p /data/local/tmp/cts/companion" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/cts/companion" />
+
+        <option name="run-command" value="am wait-for-broadcast-idle" />
+    </target_preparer>
+
+    <!-- Push test APKs into the temporary directory. -->
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="push" value="CtsCompanionTestApp.apk->/data/local/tmp/cts/companion/CtsCompanionTestApp.apk" />
+    </target_preparer>
+</configuration>
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt
new file mode 100644
index 0000000..b7a70f2
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociateSelfManagedTest.kt
@@ -0,0 +1,147 @@
+/*
+ * 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.companion.cts.core
+
+import android.Manifest.permission.REQUEST_COMPANION_PROFILE_WATCH
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+import kotlin.test.assertNull
+
+/**
+ * Test CDM APIs for requesting establishing new associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociateSelfManagedTest
+ *
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociateSelfManagedTest : CoreTestBase() {
+
+    @Test
+    fun test_associate_selfManaged_requiresPermission() {
+        val request: AssociationRequest = AssociationRequest.Builder()
+                .setSelfManaged(true)
+                .setDisplayName(DEVICE_DISPLAY_NAME)
+                .build()
+        val callback = RecordingCallback()
+
+        // Attempts to create a "self-managed" association without the MANAGE_COMPANION_DEVICES
+        // permission should lead to a SecurityException being thrown.
+        assertFailsWith(SecurityException::class) {
+            cdm.associate(request, SIMPLE_EXECUTOR, callback)
+        }
+        assertEmpty(callback.invocations)
+
+        // Same call with the MANAGE_COMPANION_DEVICES permissions should succeed.
+        withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+            cdm.associate(request, SIMPLE_EXECUTOR, callback)
+        }
+    }
+
+    @Test
+    fun test_associate_selfManaged_nullProfile_leadsToNoUiFlow() {
+        val request: AssociationRequest = AssociationRequest.Builder()
+                .setSelfManaged(true)
+                .setDisplayName(DEVICE_DISPLAY_NAME)
+                .build()
+        val callback = RecordingCallback()
+
+        callback.assertInvokedByActions {
+            withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+                cdm.associate(request, SIMPLE_EXECUTOR, callback)
+            }
+        }
+
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // onAssociationCreated() method.
+        assertEquals(1, callback.invocations.size)
+        val associationInvocation = callback.invocations.first()
+        assertIs<OnAssociationCreated>(associationInvocation)
+        with(associationInvocation.associationInfo) {
+            assertEquals(actual = displayName, expected = DEVICE_DISPLAY_NAME)
+            assertNull(deviceProfile)
+        }
+
+        // Check that the newly created association is included in
+        // CompanionDeviceManager.getMyAssociations()
+        assertContentEquals(
+            actual = cdm.myAssociations,
+            expected = listOf(associationInvocation.associationInfo)
+        )
+    }
+
+    @Test
+    fun test_associate_selfManaged_alreadyRoleHolder_leadsToNoUiFlow() =
+            targetApp.withRole(ROLE_WATCH) {
+                val request: AssociationRequest = AssociationRequest.Builder()
+                        .setSelfManaged(true)
+                        .setDeviceProfile(DEVICE_PROFILE_WATCH)
+                        .setDisplayName(DEVICE_DISPLAY_NAME)
+                        .build()
+                val callback = RecordingCallback()
+
+                callback.assertInvokedByActions {
+                    withShellPermissionIdentity(
+                        REQUEST_COMPANION_SELF_MANAGED,
+                        REQUEST_COMPANION_PROFILE_WATCH
+                    ) {
+                        cdm.associate(request, SIMPLE_EXECUTOR, callback)
+                    }
+                }
+
+                // Check callback invocations: there should have been exactly 1 invocation of the
+                // onAssociationCreated().
+                assertEquals(1, callback.invocations.size)
+                val associationInvocation = callback.invocations.first()
+                assertIs<OnAssociationCreated>(associationInvocation)
+                with(associationInvocation.associationInfo) {
+                    assertEquals(actual = displayName, expected = DEVICE_DISPLAY_NAME)
+                    assertEquals(actual = deviceProfile, expected = ROLE_WATCH)
+                }
+
+                // Check that the newly created association is included in
+                // CompanionDeviceManager.getMyAssociations()
+                assertContentEquals(
+                    actual = cdm.myAssociations,
+                    expected = listOf(associationInvocation.associationInfo)
+                )
+            }
+
+    override fun tearDown() {
+        targetApp.removeFromHoldersOfRole(ROLE_WATCH)
+        super.tearDown()
+    }
+
+    companion object {
+        private const val DEVICE_DISPLAY_NAME = "My device"
+        private const val ROLE_WATCH = DEVICE_PROFILE_WATCH
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt
new file mode 100644
index 0000000..8e0e90d
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociateTest.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.companion.cts.core
+
+import android.companion.AssociationRequest
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+
+/**
+ * Test CDM APIs for requesting establishing new associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociateTest : CoreTestBase() {
+
+    @Test
+    fun test_associate() {
+        val request: AssociationRequest = AssociationRequest.Builder()
+                .build()
+        val callback = RecordingCallback()
+
+        callback.assertInvokedByActions {
+            cdm.associate(request, SIMPLE_EXECUTOR, callback)
+        }
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // onAssociationPending() method.
+        assertEquals(1, callback.invocations.size)
+        assertIs<OnAssociationPending>(callback.invocations.first())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt
new file mode 100644
index 0000000..e999c86
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationRequestBuilderTest.kt
@@ -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 android.companion.cts.core
+
+import android.companion.AssociationRequest
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.BluetoothDeviceFilter
+import android.companion.cts.common.assertEmpty
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertFalse
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+/**
+ * Test [android.companion.AssociationRequest.Builder].
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationRequestBuilderTest
+ */
+@RunWith(AndroidJUnit4::class)
+class AssociationRequestBuilderTest {
+
+    @Test
+    fun test_defaultValues() {
+        val request = AssociationRequest.Builder()
+                .build()
+
+        request.apply {
+            assertNull(deviceProfile)
+            assertNull(displayName)
+
+            assertNotNull(deviceFilters)
+            assertEmpty(deviceFilters)
+
+            assertFalse(isSelfManaged)
+            assertFalse(isForceConfirmation)
+            assertFalse(isSingleDevice)
+        }
+    }
+
+    @Test
+    fun test_setters() {
+        val deviceFilterA = createBluetoothDeviceFilter("00:00:00:00:00:AA")
+        val deviceFilterB = createBluetoothDeviceFilter("00:00:00:00:00:BB")
+        val request = AssociationRequest.Builder()
+                .setDeviceProfile(DEVICE_PROFILE_WATCH)
+                .setDisplayName(DISPLAY_NAME)
+                .setSelfManaged(true)
+                .setForceConfirmation(true)
+                .setSingleDevice(true)
+                .addDeviceFilter(deviceFilterA)
+                .addDeviceFilter(deviceFilterB)
+                .build()
+
+        request.apply {
+            assertEquals(actual = deviceProfile, expected = DEVICE_PROFILE_WATCH)
+            assertEquals(actual = displayName, expected = DISPLAY_NAME)
+            assertContentEquals(
+                    actual = deviceFilters,
+                    expected = listOf(deviceFilterA, deviceFilterB))
+            assertTrue(isSelfManaged)
+            assertTrue(isForceConfirmation)
+            assertTrue(isSingleDevice)
+        }
+    }
+
+    @Test
+    fun test_selfManaged_require_displayName() {
+        assertFailsWith<IllegalStateException> {
+            AssociationRequest.Builder()
+                    .setSelfManaged(true)
+                    .build()
+        }
+    }
+
+    companion object {
+        private const val DISPLAY_NAME = "My Device"
+    }
+}
+
+private fun createBluetoothDeviceFilter(address: String) = BluetoothDeviceFilter.Builder()
+        .setAddress(address)
+        .build()
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt
new file mode 100644
index 0000000..a523a62
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsChangedListenerTest.kt
@@ -0,0 +1,155 @@
+/*
+ * 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.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.cts.common.BACKGROUND_THREAD_EXECUTOR
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_A
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCdmEventObserver
+import android.companion.cts.common.RecordingCdmEventObserver.AssociationChange
+import android.companion.cts.common.RecordingCdmEventObserver.CdmCallback
+import android.companion.cts.common.RecordingOnAssociationsChangedListener
+import android.companion.cts.common.Repeat
+import android.companion.cts.common.RepeatRule
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+
+/**
+ * Test CDM APIs for listening for changes to [android.companion.AssociationInfo].
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationsChangedListenerTest
+ *
+ * @see android.companion.CompanionDeviceManager.OnAssociationsChangedListener
+ * @see android.companion.CompanionDeviceManager.addOnAssociationsChangedListener
+ * @see android.companion.CompanionDeviceManager.removeOnAssociationsChangedListener
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociationsChangedListenerTest : CoreTestBase() {
+    @get:Rule
+    val repeatRule = RepeatRule()
+
+    @Test
+    fun test_addOnAssociationsChangedListener_requiresPermission() {
+        /**
+         * Attempts to add a listener without [MANAGE_COMPANION_DEVICES] permission should
+         * throw a [SecurityException] and should not change the existing associations.
+         */
+        assertFailsWith(SecurityException::class) {
+            cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, NO_OP_LISTENER)
+        }
+
+        /** Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed */
+        withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+            cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, NO_OP_LISTENER)
+
+            /** Succeeded, now remove. */
+            cdm.removeOnAssociationsChangedListener(NO_OP_LISTENER)
+        }
+    }
+
+    @Test
+    fun test_addOnAssociationsChangedListener() {
+        val listener = RecordingOnAssociationsChangedListener()
+
+        withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+            cdm.addOnAssociationsChangedListener(SIMPLE_EXECUTOR, listener)
+        }
+
+        listener.assertInvokedByActions {
+            testApp.associate(MAC_ADDRESS_A)
+        }
+
+        listener.invocations[0].let { associations ->
+            assertEquals(actual = associations.size, expected = 1)
+            assertEquals(actual = associations[0].deviceMacAddress, expected = MAC_ADDRESS_A)
+            assertEquals(actual = associations[0].packageName, expected = TEST_APP_PACKAGE_NAME)
+        }
+
+        listener.clearRecordedInvocations()
+
+        withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+            cdm.removeOnAssociationsChangedListener(listener)
+        }
+
+        testApp.disassociate(MAC_ADDRESS_A)
+        // The listener shouldn't get involved after removed the onAssociationsChangedListener.
+        assertEmpty(listener.invocations)
+    }
+
+    @Test
+    @Repeat(10)
+    fun test_associationChangeListener_notifiedBefore_cdmCallback() {
+        val request: AssociationRequest = AssociationRequest.Builder()
+            .setSelfManaged(true)
+            .setDisplayName(DEVICE_DISPLAY_NAME_A)
+            .build()
+
+        val observer = RecordingCdmEventObserver()
+
+        // preparation: register the observer as an association change listener
+        withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+            cdm.addOnAssociationsChangedListener(BACKGROUND_THREAD_EXECUTOR, observer)
+        }
+
+        // test scenario: carry out an association and assert that
+        // the association listener is notified BEFORE the CDM observer
+        observer.assertInvokedByActions(minOccurrences = 2) {
+            withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+                // in order to make sure the OnAssociationsChangedListener and
+                // CompanionDeviceManager.Callback callbacks are recorded in the right order use
+                // the same Executor - BACKGROUND_THREAD_EXECUTOR - here that we used for
+                // addOnAssociationsChangedListener above.
+                cdm.associate(request, BACKGROUND_THREAD_EXECUTOR, observer)
+            }
+        }
+
+        // we should have observed exactly two events
+        assertEquals(2, observer.invocations.size)
+        val (event1, event2) = observer.invocations
+
+        // the event we observed first should be an association change
+        assertIs<AssociationChange>(event1)
+        // there should be exactly one association
+        assertEquals(1, event1.associations.size)
+        val associationInfoFromListener = event1.associations.first()
+        assertEquals(
+            actual = associationInfoFromListener.displayName,
+            expected = DEVICE_DISPLAY_NAME_A
+        )
+
+        // the second event should be the callback invocation
+        assertIs<CdmCallback>(event2)
+        val callbackInvocation = event2.invocation
+        assertIs<OnAssociationCreated>(callbackInvocation)
+
+        val associationInfoFromCallback = callbackInvocation.associationInfo
+        assertEquals(associationInfoFromListener, associationInfoFromCallback)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt
new file mode 100644
index 0000000..332816a
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/AssociationsCleanUpTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.companion.cts.core
+
+import android.annotation.UserIdInt
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.AppHelper
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.waitFor
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests if associations that belong to a package are removed when the package is uninstalled or its
+ * data is cleared.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:AssociationsCleanUpTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class AssociationsCleanUpTest : CoreTestBase() {
+
+    @Test
+    fun test_associationsRemoved_onPackageDataCleared() {
+        testApp.associate(MAC_ADDRESS_A)
+        testApp.associate(MAC_ADDRESS_B)
+        targetApp.associate(MAC_ADDRESS_C)
+
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        testApp.packageName to MAC_ADDRESS_A,
+                        testApp.packageName to MAC_ADDRESS_B,
+                        targetApp.packageName to MAC_ADDRESS_C
+                ))
+
+        /** Clear test app's data. */
+        testApp.clearData()
+        assertAssociationsRemovedFor(testApp)
+
+        /** Only Test App's associations should have been removed. Others - should remain. */
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        targetApp.packageName to MAC_ADDRESS_C
+                ))
+    }
+
+    @Test
+    fun test_associationsRemoved_onPackageRemoved() {
+        testApp.associate(MAC_ADDRESS_A)
+        testApp.associate(MAC_ADDRESS_B)
+        targetApp.associate(MAC_ADDRESS_C)
+
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        testApp.packageName to MAC_ADDRESS_A,
+                        testApp.packageName to MAC_ADDRESS_B,
+                        targetApp.packageName to MAC_ADDRESS_C
+                ))
+
+        /** Uninstall test app. */
+        testApp.uninstall()
+        assertAssociationsRemovedFor(testApp)
+
+        /** Only Test App's associations should have been removed. Others - should remain. */
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        targetApp.packageName to MAC_ADDRESS_C
+                ))
+    }
+
+    private fun assertAssociationsRemovedFor(app: AppHelper) = waitFor {
+        withShellPermissionIdentity {
+            cdm.getAssociationForPackage(app.userId, app.packageName).isEmpty()
+        }
+    }.let { removed ->
+        if (!removed)
+            throw AssertionError("Associations for ${app.packageName} were not removed.")
+    }
+}
+
+private fun CompanionDeviceManager.getAssociationForPackage(
+    @UserIdInt userId: Int,
+    packageName: String
+): List<AssociationInfo> = allAssociations.filter { it.belongsToPackage(userId, packageName) }
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt
new file mode 100644
index 0000000..198c4ad
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/BasicTest.kt
@@ -0,0 +1,37 @@
+/*
+ * 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.companion.cts.core
+
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertNotNull
+
+/**
+ * Tests most basic CDM APis.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:BasicTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class BasicTest : CoreTestBase() {
+    @Test
+    fun test_manager_isNotNull() {
+        assertNotNull(cdm)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt b/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt
new file mode 100644
index 0000000..a25ee93
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/Constants.kt
@@ -0,0 +1,20 @@
+/*
+ * 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.companion.cts.core
+
+const val TEST_APP_PACKAGE_NAME = "android.os.cts.companiontestapp"
+const val TEST_APP_APK_PATH = "/data/local/tmp/cts/companion/CtsCompanionTestApp.apk"
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt b/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt
new file mode 100644
index 0000000..07795fd
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/CoreTestBase.kt
@@ -0,0 +1,31 @@
+package android.companion.cts.core
+
+import android.annotation.CallSuper
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.AppHelper
+import android.companion.cts.common.TestBase
+import kotlin.test.assertTrue
+
+open class CoreTestBase : TestBase() {
+    protected val testApp = AppHelper(
+            instrumentation, userId, TEST_APP_PACKAGE_NAME, TEST_APP_APK_PATH)
+
+    @CallSuper
+    override fun setUp() {
+        super.setUp()
+
+        // Make sure test app is installed.
+        with(testApp) {
+            if (!isInstalled()) install()
+            assertTrue("Test app $packageName is not installed") { isInstalled() }
+        }
+    }
+
+    protected val NO_OP_LISTENER: CompanionDeviceManager.OnAssociationsChangedListener =
+        CompanionDeviceManager.OnAssociationsChangedListener { }
+
+    protected val NO_OP_CALLBACK: CompanionDeviceManager.Callback =
+        object : CompanionDeviceManager.Callback() {
+            override fun onFailure(error: CharSequence?) = Unit
+        }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt
new file mode 100644
index 0000000..fd14987
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/DeviceProfilesTest.kt
@@ -0,0 +1,125 @@
+package android.companion.cts.core
+
+import android.app.role.RoleManager.ROLE_ASSISTANT
+import android.app.role.RoleManager.ROLE_BROWSER
+import android.app.role.RoleManager.ROLE_CALL_REDIRECTION
+import android.app.role.RoleManager.ROLE_CALL_SCREENING
+import android.app.role.RoleManager.ROLE_DIALER
+import android.app.role.RoleManager.ROLE_EMERGENCY
+import android.app.role.RoleManager.ROLE_HOME
+import android.app.role.RoleManager.ROLE_SMS
+import android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION
+import android.app.role.RoleManager.ROLE_SYSTEM_WELLBEING
+import android.companion.AssociationRequest
+import android.companion.cts.common.DEVICE_PROFILE_TO_PERMISSION
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+
+/**
+ * Test CDM device profiles.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DeviceProfilesTest
+ *
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+ * @see android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+ * @see android.companion.CompanionDeviceManager.associate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class DeviceProfilesTest : CoreTestBase() {
+    /** Test that all supported device profiles require a permission. */
+    @Test
+    fun test_supportedProfiles() {
+        val callback = RecordingCallback()
+        DEVICE_PROFILE_TO_PERMISSION.forEach { (profile, permission) ->
+            callback.clearRecordedInvocations()
+            val request = buildRequest(deviceProfile = profile)
+
+            // Should fail if called without the required permissions.
+            assertFailsWith(SecurityException::class) {
+                cdm.associate(request, SIMPLE_EXECUTOR, callback)
+            }
+            // Make sure callback wasn't invoked.
+            assertEmpty(callback.invocations)
+
+            // Should succeed when called with the required permission.
+            assertNotNull(permission, "Profile should require a permission")
+            // Associate and wait for callback.
+            callback.assertInvokedByActions {
+                withShellPermissionIdentity(permission) {
+                    cdm.associate(request, SIMPLE_EXECUTOR, callback)
+                }
+            }
+            // Make sure it's the right callback.
+            assertEquals(1, callback.invocations.size)
+            assertIs<OnAssociationPending>(callback.invocations.first())
+        }
+    }
+
+    /** Test that CDM rejects "arbitrary" profiles. */
+    @Test
+    fun test_unsupportedProfiles() = withShellPermissionIdentity {
+        val callback = RecordingCallback()
+        UNSUPPORTED_PROFILES.forEach { profile ->
+            val request = buildRequest(deviceProfile = profile)
+            // Should fail if called without the required permissions.
+            assertFailsWith(IllegalArgumentException::class) {
+                cdm.associate(request, SIMPLE_EXECUTOR, callback)
+            }
+            // Make sure callback wasn't invoked.
+            assertEmpty(callback.invocations)
+        }
+    }
+
+    /** Test that the null profile is supported and does not require a permission. */
+    @Test
+    fun test_nullProfile() {
+        val callback = RecordingCallback()
+        val request = buildRequest(deviceProfile = null)
+
+        callback.assertInvokedByActions {
+            // Should not require a permission.
+            cdm.associate(request, SIMPLE_EXECUTOR, callback)
+        }
+        // Make sure it's the right callback.
+        assertEquals(1, callback.invocations.size)
+        assertIs<OnAssociationPending>(callback.invocations.first())
+    }
+
+    private fun buildRequest(deviceProfile: String?) = AssociationRequest.Builder()
+            .apply { deviceProfile?.let { setDeviceProfile(it) } }
+            .build()
+
+    companion object {
+        val UNSUPPORTED_PROFILES = setOf(
+                // Each supported device profile is backed by a role. However, other roles should
+                // not be treated as device profiles.
+                ROLE_ASSISTANT,
+                ROLE_BROWSER,
+                ROLE_DIALER,
+                ROLE_SMS,
+                ROLE_EMERGENCY,
+                ROLE_HOME,
+                ROLE_CALL_REDIRECTION,
+                ROLE_CALL_SCREENING,
+                ROLE_SYSTEM_WELLBEING,
+                ROLE_SYSTEM_SUPERVISION,
+                // Other arbitrarily Strings should not be supported either.
+                "watch",
+                "auto",
+                "companion_device",
+                ""
+        )
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt
new file mode 100644
index 0000000..2184fdc
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/DisassociateTest.kt
@@ -0,0 +1,156 @@
+/*
+ * 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.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.annotation.UserIdInt
+import android.companion.AssociationInfo
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.net.MacAddress
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
+
+/**
+ * Test CDM APIs for removing existing associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DisassociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.disassociate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class DisassociateTest : CoreTestBase() {
+    @Test
+    fun test_disassociate_sameApp_singleAssociation() = with(targetApp) {
+        associate(MAC_ADDRESS_A)
+
+        val associations = cdm.myAssociations
+        assertAssociations(
+                actual = associations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        cdm.disassociate(associations[0].id)
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_disassociate_sameApp_multipleAssociations() = with(targetApp) {
+        associate(MAC_ADDRESS_A)
+        associate(MAC_ADDRESS_B)
+        associate(MAC_ADDRESS_C)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A,
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_A).id)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_B).id)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(cdm.getMyAssociationLinkedTo(MAC_ADDRESS_C).id)
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_disassociate_anotherApp_requiresPermission() = with(testApp) {
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        val association = withShellPermissionIdentity {
+            cdm.getAssociationForPackage(userId, packageName, MAC_ADDRESS_A)
+        }
+
+        /**
+         * Attempts to remove another app's association without [MANAGE_COMPANION_DEVICES]
+         * permission should throw an Exception and should not change the existing associations.
+         */
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.disassociate(association.id)
+        }
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        /**
+         * Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed and remove
+         * the association.
+         */
+        withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+            cdm.disassociate(association.id)
+        }
+        assertEmpty(
+                withShellPermissionIdentity {
+                    cdm.allAssociations
+                })
+    }
+
+    @Test
+    fun test_disassociate_invalidId() {
+        assertEmpty(
+                withShellPermissionIdentity {
+                    cdm.allAssociations
+                })
+
+        assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(0) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(1) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.disassociate(-1) }
+    }
+
+    private fun CompanionDeviceManager.getMyAssociationLinkedTo(
+        macAddress: MacAddress
+    ): AssociationInfo = myAssociations.find { it.deviceMacAddress == macAddress }
+                    ?: fail("Association linked to address $macAddress does not exist")
+
+    private fun CompanionDeviceManager.getAssociationForPackage(
+        @UserIdInt userId: Int,
+        packageName: String,
+        macAddress: MacAddress
+    ): AssociationInfo = allAssociations.find {
+        it.belongsToPackage(userId, packageName) && it.deviceMacAddress == macAddress
+    } ?: fail("Association for u$userId/$packageName linked to address $macAddress does not exist")
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt
new file mode 100644
index 0000000..56fe2b3
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/LegacyDisassociateTest.kt
@@ -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.
+ */
+
+package android.companion.cts.core
+
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+/**
+ * Test legacy CDM APIs for removing existing associations (via MAC address)
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:DisassociateTest
+ *
+ * @see android.companion.CompanionDeviceManager.disassociate
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class LegacyDisassociateTest : CoreTestBase() {
+    @Test
+    fun test_legacy_disassociate_sameApp() = with(targetApp) {
+        associate(MAC_ADDRESS_A)
+        associate(MAC_ADDRESS_B)
+        associate(MAC_ADDRESS_C)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A,
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(MAC_ADDRESS_A.toString())
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(MAC_ADDRESS_B.toString())
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        cdm.disassociate(MAC_ADDRESS_C.toString())
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_legacy_disassociate_anotherApp() = with(testApp) {
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        /** Cannot remove another app's association by MAC address. */
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.disassociate(MAC_ADDRESS_A.toString())
+        }
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        /** ...not even with [MANAGE_COMPANION_DEVICES] permission. */
+        assertFailsWith(IllegalArgumentException::class) {
+            withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+                cdm.disassociate(MAC_ADDRESS_A.toString())
+            }
+        }
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+    }
+
+    @Test
+    fun test_disassociate_invalidId() {
+        assertEmpty(
+                withShellPermissionIdentity {
+                    cdm.allAssociations
+                })
+
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.disassociate(MAC_ADDRESS_A.toString())
+        }
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.disassociate(MAC_ADDRESS_B.toString())
+        }
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.disassociate(MAC_ADDRESS_C.toString())
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt
new file mode 100644
index 0000000..b37000f
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/RetrieveAssociationsTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.companion.cts.core
+
+import android.Manifest
+import android.Manifest.permission.MANAGE_COMPANION_DEVICES
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.MAC_ADDRESS_B
+import android.companion.cts.common.MAC_ADDRESS_C
+import android.companion.cts.common.assertAssociations
+import android.companion.cts.common.assertEmpty
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotEquals
+import kotlin.test.assertNotNull
+
+/**
+ * Test CDM APIs for retrieving the list of existing associations.
+ *
+ * Run: atest CtsCompanionDeviceManagerCoreTestCases:RetrieveAssociationsTest
+ *
+ * @see android.companion.CompanionDeviceManager.getAllAssociations
+ * @see android.companion.CompanionDeviceManager.getMyAssociations
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class RetrieveAssociationsTest : CoreTestBase() {
+
+    @Test
+    fun test_getMyAssociations_singleAssociation() = with(targetApp) {
+        assertEmpty(cdm.myAssociations)
+
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        disassociate(MAC_ADDRESS_A)
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_getMyAssociations_multipleAssociations() = with(targetApp) {
+        assertEmpty(cdm.myAssociations)
+
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        associate(MAC_ADDRESS_B)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A,
+                        packageName to MAC_ADDRESS_B
+                ))
+
+        associate(MAC_ADDRESS_C)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A,
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        disassociate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_B,
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        disassociate(MAC_ADDRESS_B)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(
+                        packageName to MAC_ADDRESS_C
+                ))
+
+        disassociate(MAC_ADDRESS_C)
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_getMyAssociations_otherPackages_NotIncluded() {
+        testApp.associate(MAC_ADDRESS_A)
+        assertEmpty(cdm.myAssociations)
+
+        targetApp.associate(MAC_ADDRESS_B)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(targetApp.packageName to MAC_ADDRESS_B))
+
+        testApp.associate(MAC_ADDRESS_C)
+        assertAssociations(
+                actual = cdm.myAssociations,
+                expected = setOf(targetApp.packageName to MAC_ADDRESS_B))
+
+        targetApp.disassociate(MAC_ADDRESS_B)
+        assertEmpty(cdm.myAssociations)
+    }
+
+    @Test
+    fun test_getAllAssociations_requiresPermission() {
+        /**
+         * Attempts to get the list of all associations for the (current) user without
+         * [MANAGE_COMPANION_DEVICES] permission should throw a [SecurityException].
+         */
+        assertFailsWith(SecurityException::class) {
+            cdm.allAssociations
+        }
+
+        /**
+         * Re-running with [MANAGE_COMPANION_DEVICES] permissions: now should succeed and return a
+         * non-null list.
+         */
+        assertNotNull(
+                withShellPermissionIdentity(MANAGE_COMPANION_DEVICES) {
+                    cdm.allAssociations
+                })
+    }
+
+    @Test
+    fun test_getAllAssociations_sameApp() = with(targetApp) {
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        disassociate(MAC_ADDRESS_A)
+        assertEmpty(withShellPermissionIdentity { cdm.allAssociations })
+    }
+
+    @Test
+    fun test_getAllAssociations_otherApps() = with(testApp) {
+        associate(MAC_ADDRESS_A)
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        packageName to MAC_ADDRESS_A
+                ))
+
+        disassociate(MAC_ADDRESS_A)
+        assertEmpty(withShellPermissionIdentity { cdm.allAssociations })
+    }
+
+    @Test
+    fun test_getAllAssociations_sameAndOtherApps() {
+        targetApp.associate(MAC_ADDRESS_A)
+        testApp.associate(MAC_ADDRESS_B)
+
+        assertAssociations(
+                actual = withShellPermissionIdentity { cdm.allAssociations },
+                expected = setOf(
+                        targetApp.packageName to MAC_ADDRESS_A,
+                        testApp.packageName to MAC_ADDRESS_B
+                ))
+    }
+
+    @Test
+    fun test_ObservePresence_updatesAssociationInfo() {
+        targetApp.associate(MAC_ADDRESS_A)
+
+        // Ensure the new association is stored in the CDM
+        val associationsBeforeChange = cdm.myAssociations
+        assertEquals(1, associationsBeforeChange.size)
+        val associationInfo = cdm.myAssociations.first()
+
+        // update the underlying association
+        withShellPermissionIdentity(Manifest.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE) {
+            cdm.startObservingDevicePresence(MAC_ADDRESS_A.toString())
+        }
+
+        // Assert the association change was reflected
+        val associationsAfterUpdate = cdm.myAssociations
+        assertEquals(1, associationsAfterUpdate.size)
+        assertNotEquals(
+            actual = associationsAfterUpdate.first(),
+            illegal = associationInfo
+        )
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt b/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt
new file mode 100644
index 0000000..694f8d9
--- /dev/null
+++ b/tests/tests/companion/core/src/android/companion/cts/core/SelfPresenceReportingTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.companion.cts.core
+
+import android.Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+import android.companion.AssociationRequest
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_A
+import android.companion.cts.common.DEVICE_DISPLAY_NAME_B
+import android.companion.cts.common.MAC_ADDRESS_A
+import android.companion.cts.common.PrimaryCompanionService
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.SecondaryCompanionService
+import android.companion.cts.common.assertEmpty
+import android.companion.cts.common.waitFor
+import android.os.SystemClock.sleep
+import android.platform.test.annotations.AppModeFull
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertContentEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertIs
+import kotlin.test.assertTrue
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlin.test.assertFailsWith
+
+/**
+ * Tests CDM APIs for notifying the presence of status of the companion devices for self-managed
+ * associations.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerCoreTestCases:SelfPresenceReportingTest
+ *
+ * @see android.companion.CompanionDeviceManager.notifyDeviceAppeared
+ * @see android.companion.CompanionDeviceManager.notifyDeviceDisappeared
+ * @see android.companion.CompanionDeviceService.onDeviceAppeared
+ * @see android.companion.CompanionDeviceService.onDeviceDisappeared
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(AndroidJUnit4::class)
+class SelfPresenceReportingTest : CoreTestBase() {
+
+    @Test
+    fun test_primaryService_isBound() =
+            withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+        val associationId = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_A)
+
+        cdm.notifyDeviceAppeared(associationId)
+
+        assertTrue("Both Services - Primary and Secondary - should be bound now") {
+            waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+                PrimaryCompanionService.isBound && SecondaryCompanionService.isBound
+            }
+        }
+
+        // Check that only the primary services has received the onDeviceAppeared() callback...
+        PrimaryCompanionService.waitAssociationToAppear(associationId)
+        assertContentEquals(
+                actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+                expected = setOf(associationId)
+        )
+        // ... while the non-primary service - has NOT. (Give it 1 more second.)
+        sleep(1000)
+        assertEmpty(SecondaryCompanionService.connectedDevices)
+
+        assertFalse("Both Services - Primary and Secondary - should stay bound") {
+            waitFor(timeout = 3.seconds, interval = 1.seconds) {
+                !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+            }
+        }
+
+        cdm.notifyDeviceDisappeared(associationId)
+
+        // Check that only the primary services has received the onDeviceDisappeared() callback.
+        PrimaryCompanionService.waitAssociationToDisappear(associationId)
+        assertEmpty(PrimaryCompanionService.connectedDevices)
+
+        assertTrue("Both Services - Primary and Secondary - should be unbound now") {
+            waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+                !PrimaryCompanionService.isBound && !SecondaryCompanionService.isBound
+            }
+        }
+    }
+
+    @Test
+    @Ignore("b/211398735")
+    fun test_multipleDevices_sameApplication() {
+        val idA = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_A)
+        val idB = createSelfManagedAssociation(DEVICE_DISPLAY_NAME_B)
+
+        cdm.notifyDeviceAppeared(idA)
+
+        assertTrue("Both Services - Primary and Secondary - should be bound now") {
+            waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+                PrimaryCompanionService.isBound && SecondaryCompanionService.isBound
+            }
+        }
+
+        // Check that only the primary services has received the onDeviceAppeared() callback...
+        PrimaryCompanionService.waitAssociationToAppear(idA)
+        assertContentEquals(
+            actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+            expected = setOf(idA)
+        )
+        // ... while the non-primary service - has NOT. (Give it 1 more second.)
+        sleep(1000)
+        assertEmpty(SecondaryCompanionService.connectedDevices)
+
+        cdm.notifyDeviceAppeared(idB)
+
+        // Check that only the primary services has received the onDeviceAppeared() callback...
+        PrimaryCompanionService.waitAssociationToAppear(idB)
+        assertContentEquals(
+            actual = PrimaryCompanionService.associationIdsForConnectedDevices,
+            expected = setOf(idA, idB)
+        )
+
+        // Make sure both services stay bound.
+        assertFalse("Both Services - Primary and Secondary - should stay bound") {
+            waitFor(timeout = 3.seconds, interval = 1.seconds) {
+                !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+            }
+        }
+
+        // "Disconnect" first device (A).
+        cdm.notifyDeviceDisappeared(idA)
+
+        PrimaryCompanionService.waitAssociationToDisappear(idA)
+        // Both services should stay bound for as long as there is at least
+        // one connected device (B).
+        assertFalse("Both Services - Primary and Secondary - should stay bound") {
+            waitFor(timeout = 3.seconds, interval = 1.seconds) {
+                !PrimaryCompanionService.isBound || !SecondaryCompanionService.isBound
+            }
+        }
+
+        // "Disconnect" second device (B).
+        cdm.notifyDeviceDisappeared(idB)
+
+        PrimaryCompanionService.waitAssociationToDisappear(idB)
+        assertTrue("Both Services - Primary and Secondary - should be unbound now") {
+            waitFor(timeout = 1.seconds, interval = 100.milliseconds) {
+                !PrimaryCompanionService.isBound && !SecondaryCompanionService.isBound
+            }
+        }
+    }
+
+    @Test
+    fun test_notifyAppearAndDisappear_invalidId() {
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(-1) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(0) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceAppeared(1) }
+
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(-1) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(0) }
+        assertFailsWith(IllegalArgumentException::class) { cdm.notifyDeviceDisappeared(1) }
+    }
+
+    @Test
+    fun test_notifyAppears_requires_selfManagedAssociation() {
+        targetApp.associate(MAC_ADDRESS_A)
+
+        val id = cdm.myAssociations[0].id
+
+        // notifyDeviceAppeared can only be called for self-managed associations.
+        assertFailsWith(IllegalArgumentException::class) {
+            cdm.notifyDeviceAppeared(id)
+        }
+    }
+
+    private fun createSelfManagedAssociation(displayName: String): Int {
+        val callback = RecordingCallback()
+        val request: AssociationRequest = AssociationRequest.Builder()
+            .setSelfManaged(true)
+            .setDisplayName(displayName)
+            .build()
+        callback.assertInvokedByActions {
+            withShellPermissionIdentity(REQUEST_COMPANION_SELF_MANAGED) {
+                cdm.associate(request, SIMPLE_EXECUTOR, callback)
+            }
+        }
+
+        val callbackInvocation = callback.invocations.first()
+        assertIs<OnAssociationCreated>(callbackInvocation)
+        return callbackInvocation.associationInfo.id
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/AndroidManifest.xml b/tests/tests/companion/uiautomation/AndroidManifest.xml
new file mode 100644
index 0000000..a6052fe
--- /dev/null
+++ b/tests/tests/companion/uiautomation/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.companion.cts.uiautomation">
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.companion.cts.uiautomation"
+        android:label="CompanionDeviceManager UiAutomation CTS tests">
+
+        <meta-data
+            android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/companion/uiautomation/AndroidTest.xml b/tests/tests/companion/uiautomation/AndroidTest.xml
new file mode 100644
index 0000000..9982689
--- /dev/null
+++ b/tests/tests/companion/uiautomation/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?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="Configuration for UiAutomation CTS tests for CompanionDeviceManager">
+    <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" />
+
+    <option name="not-shardable" value="true" />
+    <option name="test-suite-tag" value="cts" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsCompanionDeviceManagerUiAutomationTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.companion.cts.uiautomation" />
+    </test>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
+        <option name="run-command" value="wm dismiss-keyguard" />
+        <option name="run-command" value="am wait-for-broadcast-idle" />
+    </target_preparer>
+</configuration>
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt
new file mode 100644
index 0000000..4528b4e
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSelfManagedTest.kt
@@ -0,0 +1,98 @@
+package android.companion.cts.uiautomation
+
+import android.app.Activity
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
+import android.companion.CompanionDeviceManager
+import android.companion.cts.common.CompanionActivity
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.content.Intent
+import android.platform.test.annotations.AppModeFull
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run:
+ * atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndSelfManagedTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndSelfManagedTest(
+    profile: String?,
+    profilePermission: String?,
+    profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+    override fun setUp() {
+        super.setUp()
+
+        // TODO(b/211602270): Add support for WATCH and "null" profiles in the
+        // confirmation UI (the "self-managed" flow variant).
+        assumeFalse(profile == null)
+        assumeFalse(profile == DEVICE_PROFILE_WATCH)
+    }
+
+    @Test
+    fun test_userRejected() = super.test_userRejected(
+            singleDevice = false, selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+    @Test
+    fun test_userDismissed() = super.test_userDismissed(
+            singleDevice = false, selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+    @Test
+    fun test_userConfirmed() {
+        sendRequestAndLaunchConfirmation(selfManaged = true, displayName = DEVICE_DISPLAY_NAME)
+
+        callback.assertInvokedByActions {
+            // User "approves" the request.
+            confirmationUi.clickPositiveButton()
+        }
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // OnAssociationCreated() method.
+        assertEquals(1, callback.invocations.size)
+        val associationInvocation = callback.invocations.first()
+        assertIs<OnAssociationCreated>(associationInvocation)
+        val associationFromCallback = associationInvocation.associationInfo
+        assertEquals(actual = associationFromCallback.displayName, expected = DEVICE_DISPLAY_NAME)
+
+        // Wait until the Confirmation UI goes away.
+        confirmationUi.waitUntilGone()
+
+        // Check the result code and the data delivered via onActivityResult()
+        val (resultCode: Int, data: Intent?) = CompanionActivity.waitForActivityResult()
+        assertEquals(actual = resultCode, expected = Activity.RESULT_OK)
+        assertNotNull(data)
+        val associationFromActivityResult: AssociationInfo? =
+                data.getParcelableExtra(CompanionDeviceManager.EXTRA_ASSOCIATION)
+        assertNotNull(associationFromActivityResult)
+        // Check that the association reported back via the callback same as the association
+        // delivered via onActivityResult().
+        assertEquals(associationFromCallback, associationFromActivityResult)
+
+        // Make sure getMyAssociations() returns the same association we received via the callback
+        // as well as in onActivityResult()
+        assertContentEquals(actual = cdm.myAssociations, expected = listOf(associationFromCallback))
+    }
+
+    companion object {
+        /**
+         * List of (profile, permission, name) tuples that represent all supported profiles and
+         * null.
+         * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+         */
+        @Parameterized.Parameters(name = "profile={2}")
+        @JvmStatic
+        fun parameters() = supportedProfilesAndNull()
+
+        private const val DEVICE_DISPLAY_NAME = "My Device"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt
new file mode 100644
index 0000000..a853ae3
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndSingleDeviceTest.kt
@@ -0,0 +1,55 @@
+package android.companion.cts.uiautomation
+
+import android.platform.test.annotations.AppModeFull
+import org.junit.Ignore
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run:
+ * atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndSingleDeviceTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndSingleDeviceTest(
+    profile: String?,
+    profilePermission: String?,
+    profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+    @Test
+    @Ignore("b/211722613")
+    fun test_userRejected() =
+            super.test_userRejected(singleDevice = true, selfManaged = false, displayName = null)
+
+    @Test
+    @Ignore("b/211722613")
+    fun test_userDismissed() =
+            super.test_userDismissed(singleDevice = true, selfManaged = false, displayName = null)
+
+    @Test
+    @Ignore("b/211722613")
+    fun test_timeout() = super.test_timeout(singleDevice = true)
+
+    @Test
+    @Ignore("b/211722613")
+    fun test_userConfirmed() = super.test_userConfirmed_foundDevice(singleDevice = true) {
+        // Wait until a device is found, which should activate the "positive" button, and click on
+        // the button.
+        confirmationUi.waitUntilPositiveButtonIsEnabledAndClick()
+    }
+
+    companion object {
+        /**
+         * List of (profile, permission, name) tuples that represent all supported profiles and
+         * null.
+         * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+         */
+        @Parameterized.Parameters(name = "profile={2}")
+        @JvmStatic
+        fun parameters() = supportedProfilesAndNull()
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt
new file mode 100644
index 0000000..2aafd51
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/AssociationEndToEndTest.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.companion.cts.uiautomation
+
+import android.companion.AssociationRequest.DEVICE_PROFILE_APP_STREAMING
+import android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION
+import android.platform.test.annotations.AppModeFull
+import org.junit.Assume.assumeFalse
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+/**
+ * Tests the Association Flow end-to-end.
+ *
+ * Build/Install/Run: atest CtsCompanionDeviceManagerUiAutomationTestCases:AssociationEndToEndTest
+ */
+@AppModeFull(reason = "CompanionDeviceManager APIs are not available to the instant apps.")
+@RunWith(Parameterized::class)
+class AssociationEndToEndTest(
+    profile: String?,
+    profilePermission: String?,
+    profileName: String // Used only by the Parameterized test runner for tagging.
+) : UiAutomationTestBase(profile, profilePermission) {
+
+    override fun setUp() {
+        super.setUp()
+
+        // TODO(b/211590680): Add support for APP_STREAMING and AUTOMOTIVE_PROJECTION in the
+        // confirmation UI (the "multiple devices" flow variant).
+        assumeFalse(profile == DEVICE_PROFILE_APP_STREAMING)
+        assumeFalse(profile == DEVICE_PROFILE_AUTOMOTIVE_PROJECTION)
+    }
+
+    @Test
+    fun test_userRejected() =
+            super.test_userRejected(singleDevice = false, selfManaged = false, displayName = null)
+
+    @Test
+    fun test_userDismissed() =
+            super.test_userDismissed(singleDevice = false, selfManaged = false, displayName = null)
+
+    @Test
+    fun test_userConfirmed() = super.test_userConfirmed_foundDevice(singleDevice = false) {
+        // Wait until at least one device is found and click on it.
+        confirmationUi.waitAndClickOnFirstFoundDevice()
+    }
+
+    @Test
+    fun test_timeout() = super.test_timeout(singleDevice = false)
+
+    companion object {
+        /**
+         * List of (profile, permission, name) tuples that represent all supported profiles and
+         * null.
+         * Each test will be suffixed with "[profile=<NAME>]", e.g.: "[profile=WATCH]".
+         */
+        @Parameterized.Parameters(name = "profile={2}")
+        @JvmStatic
+        fun parameters() = supportedProfilesAndNull()
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt
new file mode 100644
index 0000000..b92e8fe
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/CompanionDeviceManagerUi.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.companion.cts.uiautomation
+
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.SearchCondition
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
+import androidx.test.uiautomator.Until
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.seconds
+
+class CompanionDeviceManagerUi(private val ui: UiDevice) {
+    val isVisible: Boolean
+        get() = ui.hasObject(CONFIRMATION_UI)
+
+    fun dismiss() {
+        if (!isVisible) return
+        // Pressing back button should close (cancel) confirmation UI.
+        ui.pressBack()
+        waitUntilGone()
+    }
+
+    fun waitUntilVisible() = ui.wait(Until.hasObject(CONFIRMATION_UI), "CDM UI has not appeared.")
+
+    fun waitUntilGone() = ui.waitShort(Until.gone(CONFIRMATION_UI), "CDM UI has not disappeared")
+
+    fun waitAndClickOnFirstFoundDevice() = ui.waitLongAndFind(
+            Until.findObject(DEVICE_LIST_WITH_ITEMS), "Device List not found or empty")
+                    .children[0].click()
+
+    fun waitUntilPositiveButtonIsEnabledAndClick() = ui.waitLongAndFind(
+        Until.findObject(POSITIVE_BUTTON), "Positive button not found or not clickable")
+            .click()
+
+    fun clickPositiveButton() = click(POSITIVE_BUTTON, "Positive button")
+
+    fun clickNegativeButton() = click(NEGATIVE_BUTTON, "Negative button")
+
+    private fun click(selector: BySelector, description: String) = ui.waitShortAndFind(
+            Until.findObject(selector), "$description  is not found")
+            .click()
+
+    companion object {
+        private const val PACKAGE_NAME = "com.android.companiondevicemanager"
+
+        private val CONFIRMATION_UI = By.pkg(PACKAGE_NAME)
+                .res(PACKAGE_NAME, "activity_confirmation")
+
+        private val CLICKABLE_BUTTON =
+                By.pkg(PACKAGE_NAME).clazz(".Button").clickable(true)
+        private val POSITIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_positive")
+        private val NEGATIVE_BUTTON = By.copy(CLICKABLE_BUTTON).res(PACKAGE_NAME, "btn_negative")
+
+        private val DEVICE_LIST = By.pkg(PACKAGE_NAME).clazz(".ListView")
+                .res(PACKAGE_NAME, "device_list")
+        private val DEVICE_LIST_ITEM = By.pkg(PACKAGE_NAME)
+                .res(PACKAGE_NAME, "list_item_device")
+        private val DEVICE_LIST_WITH_ITEMS = By.copy(DEVICE_LIST)
+                .hasChild(DEVICE_LIST_ITEM)
+    }
+
+    private fun UiDevice.wait(
+        condition: SearchCondition<Boolean>,
+        message: String,
+        timeout: Duration = 3.seconds
+    ) {
+        if (!wait(condition, timeout.inWholeMilliseconds)) error(message)
+    }
+
+    private fun UiDevice.waitShort(condition: SearchCondition<Boolean>, message: String) =
+            wait(condition, message, 1.seconds)
+
+    private fun UiDevice.waitAndFind(
+        condition: SearchCondition<UiObject2>,
+        message: String,
+        timeout: Duration = 3.seconds
+    ): UiObject2 =
+            wait(condition, timeout.inWholeMilliseconds) ?: error(message)
+
+    private fun UiDevice.waitShortAndFind(
+        condition: SearchCondition<UiObject2>,
+        message: String
+    ): UiObject2 = waitAndFind(condition, message, 1.seconds)
+
+    private fun UiDevice.waitLongAndFind(
+        condition: SearchCondition<UiObject2>,
+        message: String
+    ): UiObject2 = waitAndFind(condition, message, 10.seconds)
+}
\ No newline at end of file
diff --git a/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
new file mode 100644
index 0000000..4d1f3b3
--- /dev/null
+++ b/tests/tests/companion/uiautomation/src/android/companion/cts/uiautomation/UiAutomationTestBase.kt
@@ -0,0 +1,289 @@
+package android.companion.cts.uiautomation
+
+import android.Manifest
+import android.annotation.CallSuper
+import android.app.Activity
+import android.companion.AssociationInfo
+import android.companion.AssociationRequest
+import android.companion.BluetoothDeviceFilter
+import android.companion.BluetoothDeviceFilterUtils
+import android.companion.CompanionDeviceManager
+import android.companion.DeviceFilter
+import android.companion.cts.common.CompanionActivity
+import android.companion.cts.common.DEVICE_PROFILES
+import android.companion.cts.common.DEVICE_PROFILE_TO_NAME
+import android.companion.cts.common.DEVICE_PROFILE_TO_PERMISSION
+import android.companion.cts.common.RecordingCallback
+import android.companion.cts.common.RecordingCallback.OnAssociationCreated
+import android.companion.cts.common.RecordingCallback.OnAssociationPending
+import android.companion.cts.common.RecordingCallback.OnFailure
+import android.companion.cts.common.SIMPLE_EXECUTOR
+import android.companion.cts.common.TestBase
+import android.companion.cts.common.assertEmpty
+import android.companion.cts.common.setSystemProp
+import android.content.Intent
+import android.net.MacAddress
+import android.os.Parcelable
+import androidx.test.uiautomator.UiDevice
+import org.junit.Assume
+import org.junit.Assume.assumeFalse
+import java.util.regex.Pattern
+import kotlin.test.assertContentEquals
+import kotlin.test.assertEquals
+import kotlin.test.assertIs
+import kotlin.test.assertNotNull
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.ZERO
+import kotlin.time.Duration.Companion.seconds
+
+open class UiAutomationTestBase(
+    protected val profile: String?,
+    private val profilePermission: String?
+) : TestBase() {
+    private val uiDevice: UiDevice by lazy { UiDevice.getInstance(instrumentation) }
+    protected val confirmationUi by lazy { CompanionDeviceManagerUi(uiDevice) }
+    protected val callback by lazy { RecordingCallback() }
+
+    @CallSuper
+    override fun tearDown() {
+        super.tearDown()
+
+        CompanionActivity.safeFinish()
+        confirmationUi.dismiss()
+
+        restoreDiscoveryTimeout()
+    }
+
+    @CallSuper
+    override fun setUp() {
+        super.setUp()
+
+        assumeFalse(confirmationUi.isVisible)
+        Assume.assumeTrue(CompanionActivity.waitUntilGone())
+        uiDevice.waitForIdle()
+
+        callback.clearRecordedInvocations()
+    }
+
+    protected fun test_userRejected(
+        singleDevice: Boolean = false,
+        selfManaged: Boolean = false,
+        displayName: String? = null
+    ) = test_cancelled(singleDevice, selfManaged, displayName) {
+            // User "rejects" the request.
+            confirmationUi.clickNegativeButton()
+        }
+
+    protected fun test_userDismissed(
+        singleDevice: Boolean = false,
+        selfManaged: Boolean = false,
+        displayName: String? = null
+    ) = test_cancelled(singleDevice, selfManaged, displayName) {
+            // User "dismisses" the request.
+            uiDevice.pressBack()
+        }
+
+    private fun test_cancelled(
+        singleDevice: Boolean,
+        selfManaged: Boolean,
+        displayName: String?,
+        cancelAction: () -> Unit
+    ) {
+        sendRequestAndLaunchConfirmation(singleDevice, selfManaged, displayName)
+
+        callback.assertInvokedByActions {
+            cancelAction()
+        }
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // onFailure() method.
+        assertContentEquals(
+            actual = callback.invocations,
+            expected = listOf(OnFailure("Cancelled."))
+        )
+
+        // Wait until the Confirmation UI goes away.
+        confirmationUi.waitUntilGone()
+
+        // Check the result code delivered via onActivityResult()
+        val (resultCode: Int, _) = CompanionActivity.waitForActivityResult()
+        assertEquals(actual = resultCode, expected = Activity.RESULT_CANCELED)
+
+        // Make sure no Associations were created.
+        assertEmpty(cdm.myAssociations)
+    }
+
+    protected fun test_timeout(singleDevice: Boolean = false) {
+        setDiscoveryTimeout(1.seconds)
+
+        // The discovery timeout is 1 sec, but let's give it 2.
+        callback.assertInvokedByActions(2.seconds) {
+            // Make sure no device will match the request
+            sendRequestAndLaunchConfirmation(
+                singleDevice = singleDevice,
+                deviceFilter = UNMATCHABLE_BT_FILTER
+            )
+        }
+
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // onFailure() method.
+        assertContentEquals(
+            actual = callback.invocations,
+            expected = listOf(OnFailure("Timeout."))
+        )
+
+        // Wait until the Confirmation UI goes away.
+        confirmationUi.waitUntilGone()
+
+        // Check the result code delivered via onActivityResult()
+        val (resultCode: Int, _) = CompanionActivity.waitForActivityResult()
+        assertEquals(actual = resultCode, expected = Activity.RESULT_CANCELED)
+
+        // Make sure no Associations were created.
+        assertEmpty(cdm.myAssociations)
+    }
+
+    protected fun test_userConfirmed_foundDevice(
+        singleDevice: Boolean,
+        confirmationAction: () -> Unit
+    ) {
+        sendRequestAndLaunchConfirmation(singleDevice = singleDevice)
+
+        callback.assertInvokedByActions {
+            confirmationAction()
+        }
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // OnAssociationCreated() method.
+        assertEquals(1, callback.invocations.size)
+        val associationInvocation = callback.invocations.first()
+        assertIs<OnAssociationCreated>(associationInvocation)
+        val associationFromCallback = associationInvocation.associationInfo
+
+        // Wait until the Confirmation UI goes away.
+        confirmationUi.waitUntilGone()
+
+        // Check the result code and the data delivered via onActivityResult()
+        val (resultCode: Int, data: Intent?) = CompanionActivity.waitForActivityResult()
+        assertEquals(actual = resultCode, expected = Activity.RESULT_OK)
+        assertNotNull(data)
+        val associationFromActivityResult: AssociationInfo? =
+                data.getParcelableExtra(CompanionDeviceManager.EXTRA_ASSOCIATION)
+        assertNotNull(associationFromActivityResult)
+        // Check that the association reported back via the callback same as the association
+        // delivered via onActivityResult().
+        assertEquals(associationFromCallback, associationFromActivityResult)
+
+        // Make sure "device data" was included (for backwards compatibility), and that the
+        // MAC address extracted from this data matches the MAC address from AssociationInfo.
+        val deviceFromActivityResult: Parcelable? =
+                data.getParcelableExtra(CompanionDeviceManager.EXTRA_DEVICE)
+        assertNotNull(deviceFromActivityResult)
+
+        val deviceMacAddress =
+                BluetoothDeviceFilterUtils.getDeviceMacAddress(deviceFromActivityResult)
+        assertEquals(actual = MacAddress.fromString(deviceMacAddress),
+                expected = associationFromCallback.deviceMacAddress)
+
+        // Make sure getMyAssociations() returns the same association we received via the callback
+        // as well as in onActivityResult()
+        assertContentEquals(actual = cdm.myAssociations, expected = listOf(associationFromCallback))
+    }
+
+    protected fun sendRequestAndLaunchConfirmation(
+        singleDevice: Boolean = false,
+        selfManaged: Boolean = false,
+        displayName: String? = null,
+        deviceFilter: DeviceFilter<*>? = null
+    ) {
+        val request = AssociationRequest.Builder()
+                .apply {
+                    // Set the single-device flag.
+                    setSingleDevice(singleDevice)
+
+                    // Set the self-managed flag.
+                    setSelfManaged(selfManaged)
+
+                    // Set profile if not null.
+                    profile?.let { setDeviceProfile(it) }
+
+                    // Set display name if not null.
+                    displayName?.let { setDisplayName(it) }
+
+                    // Add device filter if not null.
+                    deviceFilter?.let { addDeviceFilter(it) }
+                }
+                .build()
+        callback.clearRecordedInvocations()
+
+        callback.assertInvokedByActions {
+            // If the REQUEST_COMPANION_SELF_MANAGED and/or the profile permission is required:
+            // run with these permissions as the Shell;
+            // otherwise: just call associate().
+            with(getRequiredPermissions(selfManaged)) {
+                if (isNotEmpty()) {
+                    withShellPermissionIdentity(*toTypedArray()) {
+                        cdm.associate(request, SIMPLE_EXECUTOR, callback)
+                    }
+                } else {
+                    cdm.associate(request, SIMPLE_EXECUTOR, callback)
+                }
+            }
+        }
+        // Check callback invocations: there should have been exactly 1 invocation of the
+        // onAssociationPending() method.
+
+        assertEquals(1, callback.invocations.size)
+        val associationInvocation = callback.invocations.first()
+        assertIs<OnAssociationPending>(associationInvocation)
+
+        // Get intent sender and clear callback invocations.
+        val pendingConfirmation = associationInvocation.intentSender
+        callback.clearRecordedInvocations()
+
+        // Launch CompanionActivity, and then launch confirmation UI from it.
+        CompanionActivity.launchAndWait(context)
+        CompanionActivity.startIntentSender(pendingConfirmation)
+
+        confirmationUi.waitUntilVisible()
+    }
+
+    private fun getRequiredPermissions(selfManaged: Boolean): List<String> =
+            mutableListOf<String>().also {
+                if (selfManaged) it += Manifest.permission.REQUEST_COMPANION_SELF_MANAGED
+                if (profilePermission != null) it += profilePermission
+            }
+
+    private fun setDiscoveryTimeout(timeout: Duration) =
+        instrumentation.setSystemProp(
+            SYS_PROP_DEBUG_TIMEOUT,
+            timeout.inWholeMilliseconds.toString()
+        )
+
+    private fun restoreDiscoveryTimeout() = setDiscoveryTimeout(ZERO)
+
+    companion object {
+        /**
+         * List of (profile, permission, name) tuples that represent all supported profiles and
+         * null.
+         */
+        @JvmStatic
+        protected fun supportedProfilesAndNull() = mutableListOf<Array<String?>>().apply {
+            add(arrayOf(null, null, "null"))
+            addAll(supportedProfiles())
+        }
+
+        /** List of (profile, permission, name) tuples that represent all supported profiles. */
+        private fun supportedProfiles(): Collection<Array<String?>> = DEVICE_PROFILES.map {
+            profile ->
+            arrayOf(profile,
+                    DEVICE_PROFILE_TO_PERMISSION[profile]!!,
+                    DEVICE_PROFILE_TO_NAME[profile]!!)
+        }
+
+        private val UNMATCHABLE_BT_FILTER = BluetoothDeviceFilter.Builder()
+                .setAddress("FF:FF:FF:FF:FF:FF")
+                .setNamePattern(Pattern.compile("This Device Does Not Exist"))
+                .build()
+
+        private const val SYS_PROP_DEBUG_TIMEOUT = "debug.cdm.discovery_timeout"
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
index b8391d7..488547d 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
@@ -17,6 +17,7 @@
 package android.provider.cts.contacts;
 
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.provider.ContactsContract;
 import android.test.AndroidTestCase;
@@ -53,4 +54,21 @@
         intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
         assertCanBeHandled(intent);
     }
+
+    public void testSetDefaultAccount() {
+        Intent intent = new Intent(ContactsContract.Settings.ACTION_SET_DEFAULT_ACCOUNT);
+        PackageManager packageManager = getContext().getPackageManager();
+        List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(intent, 0);
+        assertNotNull("Missing ResolveInfo", resolveInfoList);
+        int handlerCount = 0;
+        for (ResolveInfo resolveInfo : resolveInfoList) {
+            String packageName = resolveInfo.activityInfo.packageName;
+            if (packageManager.checkPermission(
+                    android.Manifest.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS, packageName)
+                    == PackageManager.PERMISSION_GRANTED) {
+                handlerCount++;
+            }
+        }
+        assertEquals(1, handlerCount);
+    }
 }
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
index dfeaad1..57c3006 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_AllUriTest.java
@@ -35,6 +35,8 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
 
 @LargeTest
 public class ContactsContract_AllUriTest extends AndroidTestCase {
@@ -144,7 +146,7 @@
             {"content://com.android.contacts/phone_lookup/XXX"},
             {"content://com.android.contacts/phone_lookup_enterprise/XXX"},
             {"content://com.android.contacts/aggregation_exceptions", "u"},
-            {"content://com.android.contacts/settings", "ud"},
+            {"content://com.android.contacts/settings", "iud"},
             {"content://com.android.contacts/status_updates", "ud"},
             {"content://com.android.contacts/status_updates/1"},
             {"content://com.android.contacts/search_suggest_query"},
@@ -178,8 +180,26 @@
             {"content://com.android.contacts/directory_file_enterprise/XXX?directory=0", "-"},
     };
 
+    // Contains entries for Uris that require specific values when inserting
+    private static final Map<String, ContentValues> URI_INSERT_VALUES;
+
     private static final String[] ARG1 = {"-1"};
 
+    static {
+        URI_INSERT_VALUES = new HashMap<>();
+        ContentValues values = new ContentValues();
+        values.put(SyncState.ACCOUNT_NAME, "abc");
+        values.put(SyncState.ACCOUNT_TYPE, "def");
+        URI_INSERT_VALUES.put("content://com.android.contacts/syncstate", values);
+        URI_INSERT_VALUES.put("content://com.android.contacts/syncstate/1", values);
+        URI_INSERT_VALUES.put("content://com.android.contacts/profile/syncstate", values);
+
+        values = new ContentValues();
+        values.put(ContactsContract.Settings.ACCOUNT_NAME, "abc");
+        values.put(ContactsContract.Settings.ACCOUNT_TYPE, "def");
+        URI_INSERT_VALUES.put("content://com.android.contacts/settings", values);
+    }
+
     private ContentResolver mResolver;
 
     private ArrayList<String> mFailures;
@@ -627,15 +647,13 @@
             final Uri uri = getUri(path);
 
             cv.clear();
-            if (supportsQuery(path)) {
+            if (URI_INSERT_VALUES.containsKey(path[0])) {
+                cv.putAll(URI_INSERT_VALUES.get(path[0]));
+            } else if (supportsQuery(path)) {
                 cv.put(getColumns(uri)[0], 1);
             } else {
                 cv.put("_id", 1);
             }
-            if (uri.toString().contains("syncstate")) {
-                cv.put(SyncState.ACCOUNT_NAME, "abc");
-                cv.put(SyncState.ACCOUNT_TYPE, "def");
-            }
 
             checkExecutable("insert", uri, supportsInsert(path), () -> {
                 final Uri newUri = mResolver.insert(uri, cv);
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
index cb3a2a6..4b98883 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_ContactsTest.java
@@ -142,8 +142,8 @@
         DatabaseAsserts.ContactIdPair ids = assertContactCreateDelete();
 
         String[] projection = new String[] {
-                ContactsContract.RawContacts.DIRTY,
-                ContactsContract.RawContacts.DELETED
+                RawContacts.DIRTY,
+                RawContacts.DELETED
         };
         List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids.mContactId,
                 projection);
@@ -229,8 +229,8 @@
 
         // Assert that the non-local raw contact was marked DELETED=1
         String[] projection = new String[]{
-                ContactsContract.RawContacts.DIRTY,
-                ContactsContract.RawContacts.DELETED
+                RawContacts.DIRTY,
+                RawContacts.DELETED
         };
         List<String[]> records = RawContactUtil.queryByContactId(mResolver, ids2.mContactId,
                 projection);
@@ -335,6 +335,20 @@
         assertEquals(0, rawContact.getLong(Contacts.TIMES_CONTACTED));
     }
 
+    /** Make sure local contacts are visible by default. */
+    public void testContactQuery_localContactVisibleByDefault() throws Exception {
+        // Raw contacts without an account specified are created in the local account
+        final TestRawContact localRawContact = mBuilder.newRawContact().insert().load();
+        final TestContact contact = localRawContact.getContact().load();
+
+        assertEquals(RawContacts.getLocalAccountName(mContext),
+                localRawContact.getString(RawContacts.ACCOUNT_NAME));
+        assertEquals(RawContacts.getLocalAccountType(mContext),
+                localRawContact.getString(RawContacts.ACCOUNT_TYPE));
+        assertNull(localRawContact.getString(RawContacts.DATA_SET));
+        assertEquals(1, contact.getLong(Contacts.IN_VISIBLE_GROUP));
+    }
+
     public void testProjection() throws Exception {
         final TestRawContact rawContact = mBuilder.newRawContact().insert().load();
         rawContact.newDataRow(StructuredName.CONTENT_ITEM_TYPE)
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
index 2ec7ec8..bc6ea56 100644
--- a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_RawContactsTest.java
@@ -228,7 +228,7 @@
      *
      * <p>See {@link #testRawContactCreate_noAccountUsesLocalAccount()}
      */
-    @CddTest(requirement="3.18/C-1-1,C-1-2,C-1-3")
+    @CddTest(requirement = "3.18/C-1-1,C-1-2,C-1-3")
     public void testRawContactCreate_nullAccountUsesLocalAccount() throws Exception {
         // Save a raw contact using the default local account
         TestRawContact rawContact = mBuilder.newRawContact()
diff --git a/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java
new file mode 100644
index 0000000..1adde79
--- /dev/null
+++ b/tests/tests/contactsprovider/src/android/provider/cts/contacts/ContactsContract_SettingsTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.contacts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.provider.ContactsContract.Settings;
+import android.provider.ContactsContract.SimAccount;
+import android.provider.ContactsContract.SimContacts;
+import android.provider.cts.contacts.account.StaticAccountAuthenticator;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+@MediumTest
+public class ContactsContract_SettingsTest extends AndroidTestCase {
+    // Using unique account name and type because these tests may break or be broken by
+    // other tests running. No other tests should use the following accounts.
+    private static final Account ACCT_1 = new Account("test for default account1",
+            StaticAccountAuthenticator.TYPE);
+    private static final Account ACCT_2 = new Account("test for default account2",
+            StaticAccountAuthenticator.TYPE);
+
+    private static final String SIM_ACCT_NAME = "sim account name for default account test";
+    private static final String SIM_ACCT_TYPE = "sim account type for default account test";
+    private static final int SIM_SLOT_0 = 0;
+
+    private ContentResolver mResolver;
+    private AccountManager mAccountManager;
+    private Account mInitialDefaultAccount;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mResolver = getContext().getContentResolver();
+        mAccountManager = AccountManager.get(getContext());
+
+        mAccountManager.addAccountExplicitly(ACCT_1, null, null);
+        mAccountManager.addAccountExplicitly(ACCT_2, null, null);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.addSimAccount(mResolver, SIM_ACCT_NAME, SIM_ACCT_TYPE, SIM_SLOT_0,
+                    SimAccount.ADN_EF_TYPE);
+        });
+
+        mInitialDefaultAccount = Settings.getDefaultAccount(mResolver);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        mAccountManager.removeAccount(ACCT_1, null, null);
+        mAccountManager.removeAccount(ACCT_2, null, null);
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            SimContacts.removeSimAccounts(mResolver, SIM_SLOT_0);
+        });
+
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.setDefaultAccount(mResolver, mInitialDefaultAccount);
+        });
+
+    }
+
+    /**
+     * Default account set by
+     * {@link Settings#setDefaultAccount(ContentResolver, Account)} should be
+     * returned by {@link Settings#getDefaultAccount(ContentResolver)}
+     */
+    public void testSetDefaultAccount_returnedByGetDefaultAccount() {
+        // Set default account to a system account and call get to check.
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.setDefaultAccount(mResolver, ACCT_1);
+        });
+
+        Account defaultAccount = Settings.getDefaultAccount(mResolver);
+        assertThat(defaultAccount.name).isEqualTo("test for default account1");
+        assertThat(defaultAccount.type).isEqualTo(StaticAccountAuthenticator.TYPE);
+
+        // Update default account to system account and call get to check.
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.setDefaultAccount(mResolver, ACCT_2);
+        });
+
+        defaultAccount = Settings.getDefaultAccount(mResolver);
+        assertThat(defaultAccount.name).isEqualTo("test for default account2");
+        assertThat(defaultAccount.type).isEqualTo(StaticAccountAuthenticator.TYPE);
+
+        // Update default account to NULL and check.
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.setDefaultAccount(mResolver, null);
+        });
+
+        defaultAccount = Settings.getDefaultAccount(mResolver);
+        assertThat(defaultAccount).isNull();
+
+        // Update default account to sim account and check.
+        SystemUtil.runWithShellPermissionIdentity(() -> {
+            Settings.setDefaultAccount(mResolver, new Account(SIM_ACCT_NAME, SIM_ACCT_TYPE));
+        });
+
+        defaultAccount = Settings.getDefaultAccount(mResolver);
+        assertThat(defaultAccount.name).isEqualTo(SIM_ACCT_NAME);
+        assertThat(defaultAccount.type).isEqualTo(SIM_ACCT_TYPE);
+    }
+
+    public void testSetDefaultAccount_invalidAccount() {
+        // Setting an invalid account will throw exception.
+        try {
+            SystemUtil.runWithShellPermissionIdentity(() -> {
+                Settings.setDefaultAccount(mResolver, new Account("a", "b"));
+            });
+            fail();
+        } catch (RuntimeException expected) {
+        }
+
+        Account defaultAccount = Settings.getDefaultAccount(mResolver);
+        assertThat(defaultAccount).isEqualTo(mInitialDefaultAccount);
+    }
+}
+
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 556add2..7b59702 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -368,7 +368,6 @@
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.INFO"/>
             </intent-filter>
         </activity>
         <!--Test for PackageManager-->
@@ -392,6 +391,7 @@
              android:exported="true">
             <intent-filter android:priority="-10">
                 <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.INFO"/>
                 <category android:name="android.intent.category.HOME"/>
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index 1b757e3..9b18f72 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -41,7 +41,9 @@
         <option name="push" value="CtsContentLongSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentLongSharedUserIdTestApp.apk" />
         <option name="push" value="CtsContentMaxPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxPackageNameTestApp.apk" />
         <option name="push" value="CtsContentMaxSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxSharedUserIdTestApp.apk" />
+        <option name="push" value="CtsContentMockLauncherTestApp.apk->/data/local/tmp/cts/content/CtsContentMockLauncherTestApp.apk" />
         <option name="push" value="CtsContentLongLabelNameTestApp.apk->/data/local/tmp/cts/content/CtsContentLongLabelNameTestApp.apk" />
+        <option name="push" value="CtsSyncAccountAccessStubs.apk->/data/local/tmp/cts/content/CtsSyncAccountAccessStubs.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
@@ -101,6 +103,30 @@
         <option name="push-file" key="HelloWorldResHardening_mdpi-v4.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldResHardening_mdpi-v4.apk.idsig" />
         <option name="push-file" key="malformed.apk.idsig" value="/data/local/tmp/cts/content/malformed.apk.idsig" />
         <option name="push-file" key="test-cert.x509.pem" value="/data/local/tmp/cts/content/test-cert.x509.pem" />
+        <option name="push-file" key="HelloWorldSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1.apk" />
+        <option name="push-file" key="HelloWorldSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk1Updated.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1Updated.apk" />
+        <option name="push-file" key="HelloWorldSdk1Updated.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1Updated.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk1MajorVersion2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1MajorVersion2.apk" />
+        <option name="push-file" key="HelloWorldSdk1MajorVersion2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1MajorVersion2.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk1DifferentSigner.apk" value="/data/local/tmp/cts/content/HelloWorldSdk1DifferentSigner.apk" />
+        <option name="push-file" key="HelloWorldSdk1DifferentSigner.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk1DifferentSigner.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk2.apk" />
+        <option name="push-file" key="HelloWorldSdk2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk2.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk2Updated.apk" value="/data/local/tmp/cts/content/HelloWorldSdk2Updated.apk" />
+        <option name="push-file" key="HelloWorldSdk2Updated.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk2Updated.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk3UsingSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1.apk" />
+        <option name="push-file" key="HelloWorldSdk3UsingSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1.apk.idsig" />
+        <option name="push-file" key="HelloWorldSdk3UsingSdk1And2.apk" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1And2.apk" />
+        <option name="push-file" key="HelloWorldSdk3UsingSdk1And2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldSdk3UsingSdk1And2.apk.idsig" />
+        <option name="push-file" key="HelloWorldUsingSdk1.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1.apk" />
+        <option name="push-file" key="HelloWorldUsingSdk1.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1.apk.idsig" />
+        <option name="push-file" key="HelloWorldUsingSdk1And2.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1And2.apk" />
+        <option name="push-file" key="HelloWorldUsingSdk1And2.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk1And2.apk.idsig" />
+        <option name="push-file" key="HelloWorldUsingSdk3.apk" value="/data/local/tmp/cts/content/HelloWorldUsingSdk3.apk" />
+        <option name="push-file" key="HelloWorldUsingSdk3.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldUsingSdk3.apk.idsig" />
+        <option name="push-file" key="HelloWorldNoAppStorage.apk" value="/data/local/tmp/cts/content/HelloWorldNoAppStorage.apk" />
+        <option name="push-file" key="HelloWorldNoAppStorage.apk.idsig" value="/data/local/tmp/cts/content/HelloWorldNoAppStorage.apk.idsig" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
@@ -110,6 +136,9 @@
         <option name="test-file-name" value="CtsContentPartiallyDirectBootAwareTestApp.apk" />
         <option name="test-file-name" value="CtsSyncAccountAccessStubs.apk" />
         <option name="test-file-name" value="CtsBinderPermissionTestService.apk" />
+        <option name="test-file-name" value="CtsIntentResolutionTestApp.apk" />
+        <option name="test-file-name" value="CtsIntentResolutionTestAppApi30.apk" />
+        <option name="test-file-name" value="CtsContentNoApplicationTestApp.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/content/BinderPermissionTestService/Android.bp b/tests/tests/content/BinderPermissionTestService/Android.bp
index 928e532..8ba2432 100644
--- a/tests/tests/content/BinderPermissionTestService/Android.bp
+++ b/tests/tests/content/BinderPermissionTestService/Android.bp
@@ -27,6 +27,7 @@
         "aidl/**/I*.aidl",
     ],
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/DirectBootUnawareTestApp/Android.bp b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
index 4a1a9bb..8c35eb4 100644
--- a/tests/tests/content/DirectBootUnawareTestApp/Android.bp
+++ b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index e3c2de5..e3496a3 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -42,6 +42,7 @@
     srcs: ["src5/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -56,6 +57,7 @@
     manifest: "AndroidManifestProfileable.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "vts10",
         "general-tests",
@@ -70,6 +72,7 @@
     srcs: ["src7/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -105,6 +108,7 @@
     ],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -120,6 +124,200 @@
     manifest: "AndroidManifestShell.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk1",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk1/**/*.java"],
+    manifest: "AndroidManifestSdk1.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk1Updated",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk1/**/*.java"],
+    manifest: "AndroidManifestSdk1Updated.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk1MajorVersion2",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk1/**/*.java"],
+    manifest: "AndroidManifestSdk1MajorVersion2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk1DifferentSigner",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk1/**/*.java"],
+    manifest: "AndroidManifestSdk1.xml",
+    certificate: ":cts-testkey1",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk2",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk2/**/*.java"],
+    manifest: "AndroidManifestSdk2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk2Updated",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk2/**/*.java"],
+    manifest: "AndroidManifestSdk2Updated.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk3UsingSdk1",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk3/**/*.java"],
+    manifest: "AndroidManifestSdk3UsingSdk1.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldSdk3UsingSdk1And2",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk3/**/*.java"],
+    manifest: "AndroidManifestSdk3UsingSdk1And2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldUsingSdk1",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk_user/**/*.java"],
+    manifest: "AndroidManifestUsingSdk1.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldUsingSdk1And2",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk_user/**/*.java"],
+    manifest: "AndroidManifestUsingSdk1And2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldUsingSdk3",
+    defaults: ["hello_world_defaults"],
+    srcs: ["sdk_user/**/*.java"],
+    manifest: "AndroidManifestUsingSdk3.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    v4_signature: true,
+}
+
+//-----------------------------------------------------------
+android_test {
+    name: "HelloWorldNoAppStorage",
+    defaults: ["hello_world_defaults"],
+    srcs: ["src5/**/*.java"],
+    manifest: "AndroidManifestNoAppStorage.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "mts",
         "cts",
         "vts10",
         "general-tests",
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml b/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml
new file mode 100644
index 0000000..ff95116
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestNoAppStorage.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.example.helloworld">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme"
+         android:forceQueryable="true">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <property android:name="android.internal.PROPERTY_NO_APP_DATA_STORAGE"
+                  android:value="true" />
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml
new file mode 100644
index 0000000..3c10917
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk1_1"
+     android:versionCode="2">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk1" android:versionMajor="1" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml
new file mode 100644
index 0000000..b2e6527
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1MajorVersion2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk1_2"
+     android:versionCode="2">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk1" android:versionMajor="2" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml
new file mode 100644
index 0000000..7946cbd
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk1Updated.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk1_1"
+     android:versionCode="1">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk1" android:versionMajor="1" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml
new file mode 100644
index 0000000..4e5f4dc
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk2_2"
+     android:versionCode="1">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk2" android:versionMajor="2" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml
new file mode 100644
index 0000000..7ac1f2f
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk2Updated.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk2_2"
+     android:versionCode="2">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk2" android:versionMajor="2" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml
new file mode 100644
index 0000000..cb57d19
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk3_3"
+     android:versionCode="10">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk3" android:versionMajor="3" />
+
+        <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml
new file mode 100644
index 0000000..b8a8d96
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestSdk3UsingSdk1And2.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk3_3"
+     android:versionCode="5">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <sdk-library android:name="com.test.sdk3" android:versionMajor="3" />
+
+        <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+        <uses-sdk-library android:name="com.test.sdk2" android:versionMajor="2" android:certDigest="" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
new file mode 100644
index 0000000..d24276b
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk.user">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml
new file mode 100644
index 0000000..2e60a85
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk1And2.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk.user">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <uses-sdk-library android:name="com.test.sdk1" android:versionMajor="1" android:certDigest="" />
+        <uses-sdk-library android:name="com.test.sdk2" android:versionMajor="2" android:certDigest="" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml
new file mode 100644
index 0000000..8601b88
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/AndroidManifestUsingSdk3.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     xmlns:tools="http://schemas.android.com/tools"
+     package="com.test.sdk.user">
+
+    <application android:allowBackup="true"
+         android:debuggable="true"
+         android:icon="@mipmap/ic_launcher"
+         android:label="@string/app_name"
+         android:roundIcon="@mipmap/ic_launcher_round"
+         android:supportsRtl="true"
+         android:theme="@style/AppTheme">
+        <activity android:name=".MainActivity"
+             android:label="@string/app_name"
+             android:theme="@style/AppTheme.NoActionBar"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <uses-sdk-library android:name="com.test.sdk3" android:versionMajor="3" android:certDigest="" />
+
+        <!-- (b/197936012) Remove startup provider due to test timeout issue -->
+        <provider
+            android:name="androidx.startup.InitializationProvider"
+            android:authorities="${applicationId}.androidx-startup"
+            tools:node="remove" />
+
+    </application>
+
+</manifest>
diff --git a/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..3104534
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk1/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.sdk1_1;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        System.exit(5);
+    }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..f060455
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk2/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.sdk2_2;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        System.exit(5);
+    }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..5945d02
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk3/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.sdk3_3;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        System.exit(5);
+    }
+}
diff --git a/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java
new file mode 100644
index 0000000..4ffb930
--- /dev/null
+++ b/tests/tests/content/HelloWorldApp/sdk_user/com/example/helloworld/MainActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.test.sdk.user;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+public class MainActivity extends AppCompatActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        System.exit(5);
+    }
+}
diff --git a/tests/tests/content/IntentResolutionTestApp/Android.bp b/tests/tests/content/IntentResolutionTestApp/Android.bp
new file mode 100644
index 0000000..16159cf
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsIntentResolutionTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    min_sdk_version: "29",
+}
+
+// Same app but with lower target SDK version and different package_name
+android_test_helper_app {
+    name: "CtsIntentResolutionTestAppApi30",
+    package_name: "android.content.cts.IntentResolutionTestApi30",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    target_sdk_version: "30",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    min_sdk_version: "29",
+}
diff --git a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..ac50cb1
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/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"
+        package="android.content.cts.IntentResolutionTest" >
+    <application android:hasCode="false">
+        <activity android:name="android.content.pm.cts.TestPmActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RESOLUTION_TEST"/>
+                <action android:name="android.intent.action.SELECTORTEST"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.RESOLUTION_TEST2"/>
+                <data android:scheme="http" android:mimeType="*/*" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.RESOLUTION_TEST2"/>
+                <data android:scheme="content" android:mimeType="text/plain" />
+            </intent-filter>
+        </activity>
+        <service android:name="android.content.pm.cts.TestPmService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RESOLUTION_TEST"/>
+                <action android:name="android.intent.action.SELECTORTEST"/>
+            </intent-filter>
+        </service>
+        <receiver android:name="android.content.pm.cts.PmTestReceiver"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.RESOLUTION_TEST"/>
+                <action android:name="android.intent.action.SELECTORTEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/content/MockLauncherApp/Android.bp b/tests/tests/content/MockLauncherApp/Android.bp
new file mode 100644
index 0000000..050dab1
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsContentMockLauncherTestApp",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/content/MockLauncherApp/AndroidManifest.xml b/tests/tests/content/MockLauncherApp/AndroidManifest.xml
new file mode 100644
index 0000000..cfac8f4
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/AndroidManifest.xml
@@ -0,0 +1,43 @@
+<?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.content.cts.mocklauncherapp">
+    <application android:resetEnabledSettingsOnAppDataCleared="true">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="android.content.cts.mocklauncherapp.Launcher"
+                  android:enabled="true"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.HOME"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <!-- Tests for attribute of resetEnabledSetting -->
+        <activity android:name=".MockActivity" android:exported="false"
+                  android:enabled="false" />
+        <receiver android:name=".MockReceiver" android:exported="false"
+                  android:enabled="false" />
+        <service android:name=".MockService" android:exported="false"
+                 android:enabled="false" />
+        <provider android:authorities="android.content.cts.mocklauncherapp"
+                  android:name=".MockProvider" android:exported="false"
+                  android:enabled="false" />
+    </application>
+</manifest>
diff --git a/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java b/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java
new file mode 100644
index 0000000..ddd30e1
--- /dev/null
+++ b/tests/tests/content/MockLauncherApp/src/android/content/cts/mocklauncherapp/Launcher.java
@@ -0,0 +1,21 @@
+/*
+ * 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.content.cts.mocklauncherapp;
+
+import android.app.Activity;
+
+public class Launcher extends Activity {
+}
diff --git a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
index c24d69a..c211c0c 100644
--- a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
+++ b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.bp b/tests/tests/content/SyncAccountAccessStubs/Android.bp
index e8904d7..9f1e9ab 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.bp
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["src/**/*.java"],
     sdk_version: "current",
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
index 8423733..1712e43 100644
--- a/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
+++ b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
@@ -18,6 +18,10 @@
      package="com.android.cts.stub">
 
     <application>
+        <activity android:name=".StubActivity" android:exported="true" />
+
+        <service android:name=".StubService" android:exported="true" />
+
         <service android:name=".StubAuthenticator"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java
new file mode 100644
index 0000000..29face6
--- /dev/null
+++ b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubService.java
@@ -0,0 +1,31 @@
+/*
+ * 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.stub;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+
+public class StubService extends Service {
+    private Binder mBinder = new Binder();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+}
diff --git a/tests/tests/content/TEST_MAPPING b/tests/tests/content/TEST_MAPPING
index ed0ac34..7f588e4 100644
--- a/tests/tests/content/TEST_MAPPING
+++ b/tests/tests/content/TEST_MAPPING
@@ -1,6 +1,19 @@
 {
   "presubmit": [
     {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "android.content.pm.PackageManagerTests"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.Suppress"
+        }
+      ]
+    }
+  ],
+  "presubmit-large": [
+    {
       "name": "CtsContentTestCases",
       "options": [
         {
@@ -13,17 +26,6 @@
           "include-filter": "android.content.pm.cts"
         }
       ]
-    },
-    {
-      "name": "FrameworksCoreTests",
-      "options": [
-        {
-          "include-filter": "android.content.pm.PackageManagerTests"
-        },
-        {
-          "exclude-annotation": "androidx.test.filters.Suppress"
-        }
-      ]
     }
   ]
 }
diff --git a/tests/tests/content/emptytestapp/Android.bp b/tests/tests/content/emptytestapp/Android.bp
index f66ccdf..bfcb0c6 100644
--- a/tests/tests/content/emptytestapp/Android.bp
+++ b/tests/tests/content/emptytestapp/Android.bp
@@ -22,10 +22,11 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
-    min_sdk_version : "29"
+    min_sdk_version: "29",
 }
 
 android_test_helper_app {
@@ -35,10 +36,11 @@
     manifest: "AndroidManifestLongPackageName.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
-    aaptflags: ["--warn-manifest-validation"]
+    aaptflags: ["--warn-manifest-validation"],
 }
 
 android_test_helper_app {
@@ -48,10 +50,11 @@
     manifest: "AndroidManifestLongSharedUserId.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
-    aaptflags: ["--warn-manifest-validation"]
+    aaptflags: ["--warn-manifest-validation"],
 }
 
 android_test_helper_app {
@@ -61,6 +64,7 @@
     manifest: "AndroidManifestMaxPackageName.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -73,6 +77,7 @@
     manifest: "AndroidManifestMaxSharedUserId.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -88,5 +93,21 @@
         "cts",
         "general-tests",
     ],
-    min_sdk_version : "29"
+    min_sdk_version: "29",
+}
+
+android_test_helper_app {
+    name: "CtsContentNoApplicationTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifestEmpty.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    // using 22 (< 23) to avoid soong adds application tag with attr extract-native-libs
+    min_sdk_version: "22",
+    // using 29 (< 30) to allow install an app without application tag
+    target_sdk_version: "29",
 }
diff --git a/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml b/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml
new file mode 100644
index 0000000..ee6d279
--- /dev/null
+++ b/tests/tests/content/emptytestapp/AndroidManifestEmpty.xml
@@ -0,0 +1,20 @@
+<?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.content.cts.emptytestapp.stub" >
+</manifest>
diff --git a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
index e2712e6..f715c2c 100644
--- a/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
+++ b/tests/tests/content/pm/SecureFrp/src/com/android/tests/securefrpinstall/SecureFrpInstallTest.java
@@ -64,12 +64,12 @@
     }
 
     private static void assertInstalled() throws Exception {
-        sPackageManager.getPackageInfo(TestApp.A, 0);
+        sPackageManager.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0));
     }
 
     private static void assertNotInstalled() {
         try {
-            sPackageManager.getPackageInfo(TestApp.A, 0);
+            sPackageManager.getPackageInfo(TestApp.A, PackageManager.PackageInfoFlags.of(0));
             fail("Package should not be installed");
         } catch (PackageManager.NameNotFoundException expected) {
         }
diff --git a/tests/tests/content/res/xml/file_paths.xml b/tests/tests/content/res/xml/file_paths.xml
index e8d2861..0653305 100644
--- a/tests/tests/content/res/xml/file_paths.xml
+++ b/tests/tests/content/res/xml/file_paths.xml
@@ -4,4 +4,5 @@
 <!-- Specify the storage area and path used in file provider. -->
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
     <files-path name="debug" path="debug/"/>
-</paths>
\ No newline at end of file
+    <files-path name="files" path="/" />
+</paths>
diff --git a/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java b/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java
new file mode 100644
index 0000000..ba275e2
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/ClipboardAutoClearTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.content.cts;
+
+import static com.android.server.clipboard.ClipboardService.PROPERTY_AUTO_CLEAR_ENABLED;
+import static com.android.server.clipboard.ClipboardService.PROPERTY_AUTO_CLEAR_TIMEOUT;
+import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.clipboard.ClipboardService;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class ClipboardAutoClearTest {
+    private final Context mContext = InstrumentationRegistry.getTargetContext();
+    private ClipboardManager mClipboardManager;
+    private UiDevice mUiDevice;
+    private final int mLatency = 100;
+    private final String mTestLatency = Integer.toString(mLatency);
+    private static final String LOG_TAG = "ClipboardAutoClearTest";
+    private final long mDefaultTimeout = 3600000;
+
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue("Skipping Test: Wear-Os does not support ClipboardService",
+                hasAutoFillFeature());
+        mClipboardManager = mContext.getSystemService(ClipboardManager.class);
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation().adoptShellPermissionIdentity();
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mUiDevice.wakeUp();
+        launchActivity(MockActivity.class);
+    }
+
+    private void launchActivity(Class<? extends Activity> clazz) {
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClassName(mContext.getPackageName(), clazz.getName());
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.pkg(clazz.getPackageName())), 15000);
+    }
+
+    @After
+    public void cleanUp() {
+        mClipboardManager.clearPrimaryClip();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+
+    @Test
+    public void testAutoClearEnabledDefault() {
+        String enabled = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+                PROPERTY_AUTO_CLEAR_ENABLED);
+
+        if (enabled != null) {
+            DeviceConfig.deleteProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_AUTO_CLEAR_ENABLED);
+        }
+
+        ClipboardManager clipboardManager = mClipboardManager;
+        clipboardManager.setPrimaryClip(
+                ClipData.newPlainText("Test label", "Test string"));
+        assertTrue(clipboardManager.hasPrimaryClip());
+
+        try {
+            Thread.sleep(mLatency * 10);
+        } catch (InterruptedException e) {
+            Log.w(LOG_TAG, e);
+        }
+
+        assertTrue(clipboardManager.hasPrimaryClip());
+        clipboardManager.clearPrimaryClip();
+
+        if (enabled != null) {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+                    PROPERTY_AUTO_CLEAR_ENABLED, enabled, false);
+        }
+    }
+
+    @Test
+    public void testAutoClearJob() throws Exception {
+        String enabled = getAndSetProperty(
+                PROPERTY_AUTO_CLEAR_ENABLED, "true");
+        String latency = getAndSetProperty(
+                PROPERTY_AUTO_CLEAR_TIMEOUT, mTestLatency);
+
+        ClipboardManager clipboardManager = mClipboardManager;
+        clipboardManager.setPrimaryClip(
+                ClipData.newPlainText("Test label", "Test string"));
+
+        assertTrue(clipboardManager.hasPrimaryClip());
+
+        retryUntil(() -> !clipboardManager.hasPrimaryClip(), "Auto clear did not run",
+                mLatency / 10);
+
+        getAndSetProperty(PROPERTY_AUTO_CLEAR_ENABLED, enabled);
+        getAndSetProperty(PROPERTY_AUTO_CLEAR_TIMEOUT, latency);
+
+        clipboardManager.clearPrimaryClip();
+    }
+
+    @Test
+    public void testDefaultAutoClearDuration() {
+        assertEquals(mDefaultTimeout, ClipboardService.DEFAULT_CLIPBOARD_TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Sets new value for clipboard auto clear property
+     *
+     * @return old value for the property
+     */
+    private String getAndSetProperty(String property, String newPropertyValue) {
+        String originalPropertyValue = DeviceConfig.getProperty(DeviceConfig.NAMESPACE_CLIPBOARD,
+                property);
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_CLIPBOARD, property,
+                newPropertyValue, false);
+        return originalPropertyValue;
+    }
+
+    private boolean hasAutoFillFeature() {
+        return mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOFILL);
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContentResolverTest.java b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
index 1a719eb..71bffc7 100644
--- a/tests/tests/content/src/android/content/cts/ContentResolverTest.java
+++ b/tests/tests/content/src/android/content/cts/ContentResolverTest.java
@@ -20,6 +20,8 @@
 import static android.content.ContentResolver.NOTIFY_UPDATE;
 
 import android.accounts.Account;
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentResolver.MimeTypeInfo;
@@ -38,6 +40,7 @@
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -45,6 +48,8 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.File;
@@ -1182,6 +1187,105 @@
             //expected.
         }
     }
+    // Tests registerContentObserverForAllUsers without INTERACT_ACROSS_USERS_FULL: verify
+    // SecurityException.
+    public void testRegisterContentObserverForAllUsersWithoutPermission() {
+        final MockContentObserver mco = new MockContentObserver();
+        try {
+            mContentResolver.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL);
+            fail("testRegisterContentObserverForAllUsers: "
+                    + "SecurityException expected on testRegisterContentObserverForAllUsers");
+        } catch (SecurityException se) {
+            // expected
+        }
+    }
+
+    public void testRegisterContentObserverAsUser() {
+        final MockContentObserver mco = new MockContentObserver();
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mContentResolver,
+                (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, mContext.getUser())
+        );
+        assertFalse(mco.hadOnChanged());
+
+        ContentValues values = new ContentValues();
+        values.put(COLUMN_KEY_NAME, "key10");
+        values.put(COLUMN_VALUE_NAME, 10);
+        mContentResolver.update(TABLE1_URI, values, null, null);
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return mco.hadOnChanged();
+            }
+        }.run();
+
+        mco.reset();
+        mContentResolver.unregisterContentObserver(mco);
+        assertFalse(mco.hadOnChanged());
+        mContentResolver.update(TABLE1_URI, values, null, null);
+
+        assertFalse(mco.hadOnChanged());
+    }
+
+    public void testRegisterContentObserverForAllUsers() {
+        final MockContentObserver mco = new MockContentObserver();
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mContentResolver,
+                (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, true, mco, UserHandle.ALL)
+        );
+        assertFalse(mco.hadOnChanged());
+
+        ContentValues values = new ContentValues();
+        values.put(COLUMN_KEY_NAME, "key10");
+        values.put(COLUMN_VALUE_NAME, 10);
+        mContentResolver.update(TABLE1_URI, values, null, null);
+        new PollingCheck() {
+            @Override
+            protected boolean check() {
+                return mco.hadOnChanged();
+            }
+        }.run();
+
+        mco.reset();
+        mContentResolver.unregisterContentObserver(mco);
+        assertFalse(mco.hadOnChanged());
+        mContentResolver.update(TABLE1_URI, values, null, null);
+
+        assertFalse(mco.hadOnChanged());
+
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mContentResolver,
+                    (cr) -> cr.registerContentObserverAsUser(null, false, mco, UserHandle.ALL)
+            );
+            fail("did not throw NullPointerException or IllegalArgumentException when uri is null"
+                    + ".");
+        } catch (NullPointerException e) {
+            //expected.
+        } catch (IllegalArgumentException e) {
+            // also expected
+        }
+
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mContentResolver,
+                    (cr) -> cr.registerContentObserverAsUser(TABLE1_URI, false, null,
+                            UserHandle.ALL)
+            );
+            fail("did not throw NullPointerException when register null content observer.");
+        } catch (NullPointerException e) {
+            //expected.
+        }
+
+        try {
+            mContentResolver.unregisterContentObserver(null);
+            fail("did not throw NullPointerException when unregister null content observer.");
+        } catch (NullPointerException e) {
+            //expected.
+        }
+    }
 
     public void testRegisterContentObserverDescendantBehavior() throws Exception {
         final MockContentObserver mco1 = new MockContentObserver();
@@ -1230,6 +1334,59 @@
         assertFalse(mco2.hadOnChanged());
     }
 
+    public void testRegisterContentObserverForAllUsersDescendantBehavior() throws Exception {
+        final MockContentObserver mco1 = new MockContentObserver();
+        final MockContentObserver mco2 = new MockContentObserver();
+
+        // Register one content observer with notifyDescendants set to false, and
+        // another with true.
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mContentResolver,
+                (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, false, mco1, UserHandle.ALL)
+        );
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mContentResolver,
+                (cr) -> cr.registerContentObserverAsUser(LEVEL2_URI, true, mco2, UserHandle.ALL)
+        );
+
+        // Initially nothing has happened.
+        assertFalse(mco1.hadOnChanged());
+        assertFalse(mco2.hadOnChanged());
+
+        // Fire a change with the exact URI.
+        // Should signal both observers due to exact match, notifyDescendants doesn't matter.
+        mContentResolver.notifyChange(LEVEL2_URI, null);
+        Thread.sleep(200);
+        assertTrue(mco1.hadOnChanged());
+        assertTrue(mco2.hadOnChanged());
+        mco1.reset();
+        mco2.reset();
+
+        // Fire a change with a descendant URI.
+        // Should only signal observer with notifyDescendants set to true.
+        mContentResolver.notifyChange(LEVEL3_URI, null);
+        Thread.sleep(200);
+        assertFalse(mco1.hadOnChanged());
+        assertTrue(mco2.hadOnChanged());
+        mco2.reset();
+
+        // Fire a change with an ancestor URI.
+        // Should signal both observers due to ancestry, notifyDescendants doesn't matter.
+        mContentResolver.notifyChange(LEVEL1_URI, null);
+        Thread.sleep(200);
+        assertTrue(mco1.hadOnChanged());
+        assertTrue(mco2.hadOnChanged());
+        mco1.reset();
+        mco2.reset();
+
+        // Fire a change with an unrelated URI.
+        // Should signal neither observer.
+        mContentResolver.notifyChange(TABLE1_URI, null);
+        Thread.sleep(200);
+        assertFalse(mco1.hadOnChanged());
+        assertFalse(mco2.hadOnChanged());
+    }
+
     public void testNotifyChange1() {
         final MockContentObserver mco = new MockContentObserver();
 
@@ -1489,17 +1646,27 @@
         public final boolean selfChange;
         public final Iterable<Uri> uris;
         public final int flags;
+        @UserIdInt
+        public final int userId;
 
         public Change(boolean selfChange, Iterable<Uri> uris, int flags) {
             this.selfChange = selfChange;
             this.uris = uris;
             this.flags = flags;
+            this.userId = -1;
+        }
+
+        public Change(boolean selfChange, Iterable<Uri> uris, int flags, @UserIdInt int userId) {
+            this.selfChange = selfChange;
+            this.uris = uris;
+            this.flags = flags;
+            this.userId = userId;
         }
 
         @Override
         public String toString() {
-            return String.format("onChange(%b, %s, %d)",
-                    selfChange, asSet(uris).toString(), flags);
+            return String.format("onChange(%b, %s, %d, %d)",
+                    selfChange, asSet(uris).toString(), flags, userId);
         }
 
         @Override
@@ -1508,7 +1675,7 @@
                 final Change change = (Change) other;
                 return change.selfChange == selfChange &&
                         Objects.equals(asSet(change.uris), asSet(uris)) &&
-                        change.flags == flags;
+                        change.flags == flags && change.userId == userId;
             } else {
                 return false;
             }
@@ -1536,11 +1703,13 @@
 
         @Override
         public synchronized void onChange(boolean selfChange, Collection<Uri> uris, int flags) {
-            final Change change = new Change(selfChange, uris, flags);
-            Log.v(TAG, change.toString());
+            doOnChangeLocked(selfChange, uris, flags, /*userId=*/ -1);
+        }
 
-            mHadOnChanged = true;
-            mChanges.add(change);
+        @Override
+        public synchronized void onChange(boolean selfChange, @NonNull Collection<Uri> uris,
+                @ContentResolver.NotifyFlags int flags, UserHandle user) {
+            doOnChangeLocked(selfChange, uris, flags, user.getIdentifier());
         }
 
         public synchronized boolean hadOnChanged() {
@@ -1554,5 +1723,15 @@
         public synchronized boolean hadChanges(Collection<Change> changes) {
             return mChanges.containsAll(changes);
         }
+
+        @GuardedBy("this")
+        private void doOnChangeLocked(boolean selfChange, @NonNull Collection<Uri> uris,
+                @ContentResolver.NotifyFlags int flags, @UserIdInt int userId) {
+            final Change change = new Change(selfChange, uris, flags, userId);
+            Log.v(TAG, change.toString());
+
+            mHadOnChanged = true;
+            mChanges.add(change);
+        }
     }
 }
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index a32a6c1..b742cc9 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -23,6 +23,7 @@
 
 import android.app.Activity;
 import android.app.AppOpsManager;
+import android.app.BroadcastOptions;
 import android.app.Instrumentation;
 import android.app.WallpaperManager;
 import android.content.ActivityNotFoundException;
@@ -59,6 +60,7 @@
 import android.platform.test.annotations.AppModeFull;
 import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.Suppress;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.Xml;
@@ -114,6 +116,13 @@
     private static final int BROADCAST_TIMEOUT = 10000;
     private static final int ROOT_UID = 0;
 
+    /**
+     * Shell command to broadcast {@link ResultReceiver#MOCK_ACTION} as an external app.
+     */
+    private static final String EXTERNAL_APP_BROADCAST_COMMAND =
+            "am broadcast -a " + ResultReceiver.MOCK_ACTION + " -f "
+                    + Intent.FLAG_RECEIVER_FOREGROUND;
+
     private Object mLockObj;
 
     private ArrayList<BroadcastReceiver> mRegisteredReceiverList;
@@ -595,7 +604,14 @@
     }
 
     private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-        mContext.registerReceiver(receiver, filter);
+        // All of the broadcasts for tests that use this method are sent by the local app, so by
+        // default all receivers can be registered as not exported.
+        registerBroadcastReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED);
+    }
+
+    private void registerBroadcastReceiver(BroadcastReceiver receiver, IntentFilter filter,
+            int flags) {
+        mContext.registerReceiver(receiver, filter, flags);
 
         mRegisteredReceiverList.add(receiver);
     }
@@ -1545,6 +1561,105 @@
         }.run();
     }
 
+    /**
+     * Verify the receiver should get the broadcast since it has all of the required permissions.
+     */
+    public void testSendBroadcastRequireAllOfPermissions_receiverHasAllPermissions()
+            throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireAllOfPermissions(
+                new String[] { // this test APK has both these permissions
+                        android.Manifest.permission.ACCESS_WIFI_STATE,
+                        android.Manifest.permission.ACCESS_NETWORK_STATE
+                });
+        mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+                options.toBundle());
+
+        new PollingCheck(BROADCAST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    /** The receiver should not get the broadcast if it does not have all the permissions. */
+    public void testSendBroadcastRequireAllOfPermissions_receiverHasSomePermissions()
+            throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireAllOfPermissions(
+                new String[] { // this test APK only has ACCESS_WIFI_STATE
+                        android.Manifest.permission.ACCESS_WIFI_STATE,
+                        android.Manifest.permission.NETWORK_STACK,
+                });
+
+        mContext.sendBroadcast(
+                new Intent(ResultReceiver.MOCK_ACTION), null,
+                options.toBundle());
+
+        Thread.sleep(BROADCAST_TIMEOUT);
+        assertFalse(receiver.hasReceivedBroadCast());
+    }
+
+    /**
+     * Verify the receiver will get the broadcast since it has none of the excluded permissions.
+     */
+    public void testSendBroadcastRequireNoneOfPermissions_receiverHasNoneOfExcludedPermissions()
+            throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireAllOfPermissions(
+                new String[] { // this test APK has both these permissions
+                        android.Manifest.permission.ACCESS_WIFI_STATE,
+                        android.Manifest.permission.ACCESS_NETWORK_STATE
+                });
+        options.setRequireNoneOfPermissions(
+                new String[] { // test package does not have NETWORK_STACK
+                        android.Manifest.permission.NETWORK_STACK
+                });
+        mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+                options.toBundle());
+
+        new PollingCheck(BROADCAST_TIMEOUT) {
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    /**
+     * Verify the receiver will not get the broadcast since it has one of the excluded permissions.
+     */
+    public void testSendBroadcastRequireNoneOfPermissions_receiverHasExcludedPermissions()
+            throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION));
+        BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setRequireAllOfPermissions(
+                new String[] { // this test APK has ACCESS_WIFI_STATE
+                        android.Manifest.permission.ACCESS_WIFI_STATE
+                });
+        options.setRequireNoneOfPermissions(
+                new String[] { // test package has ACCESS_NETWORK_STATE
+                        android.Manifest.permission.ACCESS_NETWORK_STATE
+                });
+        mContext.sendBroadcast(new Intent(ResultReceiver.MOCK_ACTION), null,
+                options.toBundle());
+
+        Thread.sleep(BROADCAST_TIMEOUT);
+        assertFalse(receiver.hasReceivedBroadCast());
+    }
+
     /** The receiver should get the broadcast if it has all the permissions. */
     public void testSendBroadcastWithMultiplePermissions_receiverHasAllPermissions()
             throws Exception {
@@ -1603,6 +1718,142 @@
         assertFalse(receiver.hasReceivedBroadCast());
     }
 
+    /**
+     * Starting from Android 13, a SecurityException is thrown for apps targeting this
+     * release or later that do not specify {@link Context#RECEIVER_EXPORTED} or {@link
+     * Context#RECEIVER_NOT_EXPORTED} when registering for non-system broadcasts.
+     */
+    // TODO(b/206699109): Re-enable test when instrumentation workaround is removed; without a flag
+    // specified the instrumentation workaround automatically adds RECEIVER_EXPORTED.
+    @Suppress
+    public void testRegisterReceiver_noFlags_exceptionThrown() throws Exception {
+        try {
+            final ResultReceiver receiver = new ResultReceiver();
+
+            registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION), 0);
+
+            fail("An app targeting Android 13 and registering a dynamic receiver for a "
+                    + "non-system broadcast must receive a SecurityException if "
+                    + "RECEIVER_EXPORTED or RECEIVER_NOT_EXPORTED is not specified");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * An app targeting Android 13 or later can register for system broadcasts without specifying
+     * {@link Context#RECEIVER_EXPORTED} or {@link Context@RECEIVER_NOT_EXPORTED}.
+     */
+    public void testRegisterReceiver_noFlagsProtectedBroadcast_noExceptionThrown()
+            throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+
+        // Intent.ACTION_SCREEN_OFF is a system broadcast and thus should not require a flag
+        // indicating whether the receiver is exported.
+        registerBroadcastReceiver(receiver, new IntentFilter(Intent.ACTION_SCREEN_OFF), 0);
+    }
+
+    /**
+     * An app targeting Android 13 or later can request a sticky broadcast via
+     * {@code Context#registerReceiver} without specifying {@link Context#RECEIVER_EXPORTED} or
+     * {@link Context#RECEIVER_NOT_EXPORTED}.
+     */
+    public void testRegisterReceiver_noFlagsStickyBroadcast_noExceptionThrown() throws Exception {
+        // If a null receiver is specified to Context#registerReceiver, it indicates the caller
+        // is requesting a sticky broadcast without actually registering a receiver; a flag
+        // must not be required in this case.
+        mContext.registerReceiver(null, new IntentFilter(ResultReceiver.MOCK_ACTION), 0);
+    }
+
+    /**
+     * Starting from Android 13, an app targeting this release or later must specify one of either
+     * {@link Context#RECEIVER_EXPORTED} or {@link Context#RECEIVER_NOT_EXPORTED} when registering
+     * a receiver for non-system broadcasts; however if both are specified then an
+     * {@link IllegalArgumentException} should be thrown.
+     */
+    public void testRegisterReceiver_bothFlags_exceptionThrown() throws Exception {
+        try {
+            final ResultReceiver receiver = new ResultReceiver();
+
+            registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+                    Context.RECEIVER_EXPORTED | Context.RECEIVER_NOT_EXPORTED);
+
+            fail("An app invoke invoking Context#registerReceiver with both RECEIVER_EXPORTED and"
+                    + " RECEIVER_NOT_EXPORTED set must receive an IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /**
+     * Verifies a receiver registered with {@link Context#RECEIVER_EXPORTED} can receive a
+     * broadcast from an external app.
+     *
+     * <p>The broadcast is sent as a shell command since this most closely simulates sending a
+     * broadcast from an external app; sending the broadcast via {@code
+     * ShellIdentityUtils#invokeMethodWithShellPermissionsNoReturn} is still delivered even to
+     * apps that use {@link Context#RECEIVER_NOT_EXPORTED}.
+     */
+    public void testRegisterReceiver_exported_broadcastReceived() throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+                Context.RECEIVER_EXPORTED);
+
+        SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+        new PollingCheck(BROADCAST_TIMEOUT, "The broadcast to the exported receiver"
+                + " was not received within the timeout window") {
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    /**
+     * Verifies a receiver registered with {@link Context#RECEIVER_EXPORTED_UNAUDITED} can receive
+     * a broadcast from an external app.
+     *
+     * <p>{@code Context#RECEIVER_EXPORTED_UNAUDITED} is only intended to be applied to receivers
+     * that have not yet been audited to determine their intended exported state; this test ensures
+     * this flag maintains the existing behavior of exporting the receiver until it can be
+     * evaluated.
+     */
+    public void testRegisterReceiver_exportedUnaudited_broadcastReceived() throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+                Context.RECEIVER_EXPORTED_UNAUDITED);
+
+        SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+        new PollingCheck(BROADCAST_TIMEOUT, "The broadcast to the exported receiver"
+                + " was not received within the timeout window") {
+            @Override
+            protected boolean check() {
+                return receiver.hasReceivedBroadCast();
+            }
+        }.run();
+    }
+
+    /**
+     * Verifies a receiver registered with {@link Context#RECEIVER_NOT_EXPORTED} does not receive
+     * a broadcast from an external app.
+     */
+    // TODO(b/206699109): Re-enable this test once the skip for an external app sending a broadcast
+    // to an unexported receiver is restored in BroadcastQueue.
+    @Suppress
+    public void testRegisterReceiver_notExported_broadcastNotReceived() throws Exception {
+        final ResultReceiver receiver = new ResultReceiver();
+        registerBroadcastReceiver(receiver, new IntentFilter(ResultReceiver.MOCK_ACTION),
+                Context.RECEIVER_NOT_EXPORTED);
+
+        SystemUtil.runShellCommand(EXTERNAL_APP_BROADCAST_COMMAND);
+
+        Thread.sleep(BROADCAST_TIMEOUT);
+        assertFalse(
+                "An external app must not be able to send a broadcast to a dynamic receiver "
+                        + "registered with RECEIVER_NOT_EXPORTED",
+                receiver.hasReceivedBroadCast());
+    }
+
     public void testEnforceCallingOrSelfUriPermission() {
         try {
             Uri uri = Uri.parse("content://ctstest");
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 663cbfa..b5c061d 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -60,6 +60,7 @@
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
+import java.util.function.Predicate;
 
 
 public class IntentFilterTest extends AndroidTestCase {
@@ -975,6 +976,14 @@
                 new String[]{"some.app.domain"},
                 null);
 
+        IntentFilter appWithWildcardWebLink = new Match(
+                new String[]{Intent.ACTION_VIEW},
+                new String[]{Intent.CATEGORY_BROWSABLE},
+                null,
+                new String[]{"http", "https"},
+                new String[]{"*.app.domain"},
+                null);
+
         IntentFilter browserFilterWithWildcard = new Match(
                 new String[]{Intent.ACTION_VIEW},
                 new String[]{Intent.CATEGORY_BROWSABLE},
@@ -1012,6 +1021,13 @@
                 null,
                 "https://",
                 true));
+        checkMatches(appWithWildcardWebLink,
+                new MatchCondition(NO_MATCH_DATA,
+                Intent.ACTION_VIEW,
+                new String[]{Intent.CATEGORY_BROWSABLE},
+                null,
+                "https://",
+                true));
     }
 
     public void testWriteToXml() throws IllegalArgumentException, IllegalStateException,
@@ -1774,4 +1790,20 @@
             isPrintlnCalled = true;
         }
     }
-}
\ No newline at end of file
+
+    public void testAsPredicate() throws Exception {
+        final Predicate<Intent> pred = new IntentFilter(ACTION).asPredicate();
+
+        assertTrue(pred.test(new Intent(ACTION)));
+        assertFalse(pred.test(new Intent(CATEGORY)));
+    }
+
+    public void testAsPredicateWithTypeResolution() throws Exception {
+        final ContentResolver resolver = mContext.getContentResolver();
+        final Predicate<Intent> pred = new IntentFilter(ACTION, DATA_STATIC_TYPE)
+                .asPredicateWithTypeResolution(resolver);
+
+        assertTrue(pred.test(new Intent(ACTION).setDataAndType(URI, DATA_STATIC_TYPE)));
+        assertFalse(pred.test(new Intent(ACTION).setDataAndType(URI, DATA_DYNAMIC_TYPE)));
+    }
+}
diff --git a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
index 67ec257..45b9005 100644
--- a/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
+++ b/tests/tests/content/src/android/content/cts/SharedPreferencesTest.java
@@ -22,11 +22,10 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.os.StrictMode;
-import android.os.StrictMode.ViolationInfo;
-import android.os.StrictMode.ViolationLogger;
 import android.preference.PreferenceManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
@@ -56,7 +55,7 @@
         prefs.edit().clear().commit();
         try {
             ApplicationInfo applicationInfo = mContext.getPackageManager().getApplicationInfo(
-                    mContext.getPackageName(), 0);
+                    mContext.getPackageName(), PackageManager.ApplicationInfoFlags.of(0));
             mPrefsFile = new File(applicationInfo.dataDir,
                     "shared_prefs/android.content.cts_preferences.xml");
         } catch (PackageManager.NameNotFoundException e) {
diff --git a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
index a77433f..02ee351 100644
--- a/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ApplicationInfoTest.java
@@ -38,6 +38,7 @@
 import android.content.cts.R;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.Build;
 import android.os.Environment;
@@ -67,6 +68,8 @@
             "android.content.cts.directbootunaware";
     private static final String PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME =
             "android.content.cts.partiallydirectbootaware";
+    private static final String NO_APPLICATION_PACKAGE_NAME =
+            "android.content.cts.emptytestapp.stub";
 
     private ApplicationInfo mApplicationInfo;
     private String mPackageName;
@@ -92,7 +95,8 @@
 
     @Test
     public void testWriteToParcel() throws NameNotFoundException {
-        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         Parcel p = Parcel.obtain();
         mApplicationInfo.writeToParcel(p, 0);
@@ -123,7 +127,8 @@
 
     @Test
     public void testDescribeContents() throws NameNotFoundException {
-       mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+               PackageManager.ApplicationInfoFlags.of(0));
 
         assertEquals(0, mApplicationInfo.describeContents());
     }
@@ -144,7 +149,8 @@
 
     @Test
     public void testLoadDescription() throws NameNotFoundException {
-        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         assertNull(mApplicationInfo.loadDescription(getContext().getPackageManager()));
 
@@ -155,7 +161,8 @@
 
     @Test
     public void verifyOwnInfo() throws NameNotFoundException {
-        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         assertEquals("Android TestCase", mApplicationInfo.nonLocalizedLabel);
         assertEquals(R.drawable.size_48x48, mApplicationInfo.icon);
@@ -169,7 +176,7 @@
     public void verifyDefaultValues() throws NameNotFoundException {
         // The application "com.android.cts.stub" does not have any attributes set
         mApplicationInfo = getContext().getPackageManager().getApplicationInfo(
-                SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, 0);
+                SYNC_ACCOUNT_ACCESS_STUB_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
         int currentUserId = Process.myUserHandle().getIdentifier();
 
         assertNull(mApplicationInfo.className);
@@ -215,21 +222,29 @@
     @Test
     public void testDirectBootUnawareAppIsNotEncryptionAware() throws Exception {
         ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
-                DIRECT_BOOT_UNAWARE_PACKAGE_NAME, 0);
+                DIRECT_BOOT_UNAWARE_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
         assertFalse(applicationInfo.isEncryptionAware());
     }
 
     @Test
     public void testDirectBootUnawareAppCategoryIsAccessibility() throws Exception {
         mApplicationInfo = getContext().getPackageManager().getApplicationInfo(
-                DIRECT_BOOT_UNAWARE_PACKAGE_NAME, 0);
+                DIRECT_BOOT_UNAWARE_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
         assertEquals(CATEGORY_ACCESSIBILITY, mApplicationInfo.category);
     }
 
     @Test
+    public void testDefaultAppCategoryIsUndefined() throws Exception {
+        final ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
+                NO_APPLICATION_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
+        assertEquals(CATEGORY_UNDEFINED, applicationInfo.category);
+    }
+
+    @Test
     public void testPartiallyDirectBootAwareAppIsEncryptionAware() throws Exception {
         ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(
-                PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME, 0);
+                PARTIALLY_DIRECT_BOOT_AWARE_PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         assertTrue(applicationInfo.isEncryptionAware());
     }
 
@@ -238,7 +253,8 @@
         // Make sure ApplicationInfo.writeToParcel() doesn't do the "squashing",
         // because Parcel.allowSquashing() isn't called.
 
-        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         final Parcel p = Parcel.obtain();
         mApplicationInfo.writeToParcel(p, 0);
@@ -270,7 +286,8 @@
         // Make sure ApplicationInfo.writeToParcel() does the "squashing", after
         // Parcel.allowSquashing() is called.
 
-        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName, 0);
+        mApplicationInfo = getContext().getPackageManager().getApplicationInfo(mPackageName,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         final Parcel p = Parcel.obtain();
 
@@ -317,7 +334,7 @@
                 + systemPath + vendorPath, packageName);
 
         final PackageInfo info = getContext().getPackageManager().getPackageInfo(
-                packageName.trim(), 0 /* flags */);
+                packageName.trim(), PackageManager.PackageInfoFlags.of(0));
         assertTrue(packageName + " is not vendor package.", info.applicationInfo.isVendor());
     }
 
@@ -330,7 +347,7 @@
         assumeNotNull(packageName);
 
         final PackageInfo info = getContext().getPackageManager().getPackageInfo(
-                packageName.trim(), 0 /* flags */);
+                packageName.trim(), PackageManager.PackageInfoFlags.of(0));
         assertTrue(packageName + " is not oem package.", info.applicationInfo.isOem());
     }
 
diff --git a/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java b/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
index 713c60b..c60fffe 100644
--- a/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ApplicationInfo_DisplayNameComparatorTest.java
@@ -17,8 +17,8 @@
 package android.content.pm.cts;
 
 import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ApplicationInfo.DisplayNameComparator;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.test.AndroidTestCase;
 
@@ -52,13 +52,15 @@
         info2.packageName = PACKAGE_NAME;
         assertEquals(0, mDisplayNameComparator.compare(info1, info2));
 
-        info1 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+        info1 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         info2.packageName = PACKAGE_NAME + ".2";
         assertTrue((mDisplayNameComparator.compare(info1, info2) < 0));
 
         info1 = new ApplicationInfo();
         info1.packageName = PACKAGE_NAME + ".1";
-        info2 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+        info2 = mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         assertTrue((mDisplayNameComparator.compare(info1, info2) > 0));
 
         try {
diff --git a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
index c1354f7..e7692bb 100644
--- a/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/AttributionTest.java
@@ -50,7 +50,8 @@
 
     @Test
     public void dontGetAttributions() throws Exception {
-        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME, 0);
+        PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0));
         assertNotNull(packageInfo);
         assertNull(packageInfo.attributions);
     }
@@ -58,7 +59,7 @@
     @Test
     public void getAttributionsAndVerify() throws Exception {
         PackageInfo packageInfo = sContext.getPackageManager().getPackageInfo(PACKAGE_NAME,
-                PackageManager.GET_ATTRIBUTIONS);
+                PackageManager.PackageInfoFlags.of(PackageManager.GET_ATTRIBUTIONS));
         assertNotNull(packageInfo);
         assertNotNull(packageInfo.attributions);
         assertEquals(packageInfo.attributions.length, NUM_ATTRIBUTIONS);
diff --git a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
index fa9f14e..4ca3763 100644
--- a/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ChecksumsTest.java
@@ -36,6 +36,7 @@
 
 import android.app.UiAutomation;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -51,6 +52,7 @@
 import android.content.pm.Signature;
 import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.platform.test.annotations.AppModeFull;
@@ -153,8 +155,12 @@
         return InstrumentationRegistry.getInstrumentation().getUiAutomation();
     }
 
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+
     private static PackageManager getPackageManager() {
-        return InstrumentationRegistry.getContext().getPackageManager();
+        return getContext().getPackageManager();
     }
 
     private static PackageInstaller getPackageInstaller() {
@@ -457,6 +463,7 @@
                         + "5f2888afcb71524196dda0d6dd16a6a3292bb75b431b8ff74fb60d796e882f80");
     }
 
+
     @Test
     public void testInstallerChecksumsTrustNone() throws Exception {
         installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
@@ -602,7 +609,8 @@
 
         // Using the installer's certificate(s).
         PackageManager pm = getPackageManager();
-        PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        PackageInfo packageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
         final List<Certificate> signatures = convertSignaturesToCertificates(
                 packageInfo.signingInfo.getApkContentsSigners());
 
@@ -628,13 +636,106 @@
         assertNull(checksums[2].getInstallerCertificate());
     }
 
+    @LargeTest
+    @Test
+    public void testInstallerFileChecksumsDuringInstall() throws Exception {
+        Checksum[] digestsBase = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("dd93e23bb8cdab0382fdca0d21a4f1cb"))};
+        Checksum[] digestsSplit0 = new Checksum[]{new Checksum(TYPE_WHOLE_SHA256, hexStringToBytes(
+                "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e")),
+                new Checksum(TYPE_WHOLE_MD5, hexStringToBytes("f6430e1b795ce2658c49e68d15316b2d"))};
+
+        final Certificate installerCertificate = getInstallerCertificate();
+
+        getUiAutomation().adoptShellPermissionIdentity();
+        PackageInstaller installer = null;
+        int sessionId = -1;
+        try {
+            installer = getPackageInstaller();
+            final SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
+
+            sessionId = installer.createSession(params);
+            final Session session = installer.openSession(sessionId);
+
+            writeFileToSession(session, "hw5.apk", TEST_V4_APK);
+            session.setChecksums("hw5.apk", Arrays.asList(digestsBase), NO_SIGNATURE);
+
+            writeFileToSession(session, "hw5_split0.apk", TEST_V4_SPLIT0);
+            session.setChecksums("hw5_split0.apk", Arrays.asList(digestsSplit0), NO_SIGNATURE);
+
+            // Workaround to emulate .digests file present in installation.
+            writeChecksumsToSession(session, "hw5.digests", digestsBase);
+            writeChecksumsToSession(session, "hw5_split0.digests", digestsSplit0);
+
+            File dataApp = Environment.getDataAppDirectory(null);
+
+            {
+                LocalListener receiver = new LocalListener();
+
+                session.requestChecksums("hw5.apk", 0, TRUST_ALL, getContext().getMainExecutor(),
+                        receiver);
+                ApkChecksum[] checksums = receiver.getResult();
+                assertNotNull(checksums);
+                assertEquals(checksums.length, 3);
+                // base
+                assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+                assertEquals(checksums[0].getSplitName(), null);
+                assertEquals(bytesToHexString(checksums[0].getValue()),
+                        "dd93e23bb8cdab0382fdca0d21a4f1cb");
+                assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+                assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+                assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+                assertEquals(checksums[1].getSplitName(), null);
+                assertEquals(bytesToHexString(checksums[1].getValue()),
+                        "ed8c7ae1220fe16d558e00cfc37256e6f7088ab90eb04c1bfcb39922a8a5248e");
+                assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+                assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+                assertEquals(checksums[2].getSplitName(), null);
+                assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+                assertNull(checksums[2].getInstallerPackageName());
+                assertNull(checksums[2].getInstallerCertificate());
+            }
+            {
+                LocalListener receiver = new LocalListener();
+
+                session.requestChecksums("hw5_split0.apk", 0, TRUST_ALL,
+                        getContext().getMainExecutor(), receiver);
+                ApkChecksum[] checksums = receiver.getResult();
+                assertNotNull(checksums);
+                assertEquals(checksums.length, 3);
+                // split0
+                assertEquals(checksums[0].getType(), TYPE_WHOLE_MD5);
+                assertEquals(checksums[0].getSplitName(), null);
+                assertEquals(bytesToHexString(checksums[0].getValue()),
+                        "f6430e1b795ce2658c49e68d15316b2d");
+                assertEquals(checksums[0].getInstallerPackageName(), CTS_PACKAGE_NAME);
+                assertEquals(checksums[0].getInstallerCertificate(), installerCertificate);
+                assertEquals(checksums[1].getType(), TYPE_WHOLE_SHA256);
+                assertEquals(checksums[1].getSplitName(), null);
+                assertEquals(bytesToHexString(checksums[1].getValue()),
+                        "bd9b095a49a9068498b018ce8cb7cc18d411b13a5a5f7fb417d2ff9808ae838e");
+                assertEquals(checksums[1].getInstallerPackageName(), CTS_PACKAGE_NAME);
+                assertEquals(checksums[1].getInstallerCertificate(), installerCertificate);
+                assertEquals(checksums[2].getSplitName(), null);
+                assertEquals(checksums[2].getType(), TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256);
+                assertNull(checksums[2].getInstallerPackageName());
+                assertNull(checksums[2].getInstallerCertificate());
+            }
+        } finally {
+            installer.abandonSession(sessionId);
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
     @Test
     public void testInstallerChecksumsTrustWrongInstaller() throws Exception {
         installApkWithChecksums(TEST_FIXED_APK_DIGESTS);
 
         // Using certificates from a security app, not the installer (us).
         PackageManager pm = getPackageManager();
-        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, GET_SIGNING_CERTIFICATES);
+        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
         final List<Certificate> signatures = convertSignaturesToCertificates(
                 packageInfo.signingInfo.getApkContentsSigners());
 
@@ -1019,7 +1120,8 @@
         installPackageIncrementally(TEST_FIXED_APK);
 
         PackageManager pm = getPackageManager();
-        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        PackageInfo packageInfo = pm.getPackageInfo(FIXED_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0));
         final String inPath = packageInfo.applicationInfo.getBaseCodePath();
 
         installApkWithChecksumsIncrementally(inPath);
@@ -1053,7 +1155,8 @@
 
         installPackageIncrementally(TEST_FIXED_APK);
 
-        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0));
         final String inPath = packageInfo.applicationInfo.getBaseCodePath();
 
         final byte[] signature = readSignature();
@@ -1092,7 +1195,8 @@
 
         installPackageIncrementally(TEST_FIXED_APK);
 
-        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME, 0);
+        PackageInfo packageInfo = getPackageManager().getPackageInfo(FIXED_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0));
         final String inPath = packageInfo.applicationInfo.getBaseCodePath();
 
         installApkWithChecksumsIncrementally(inPath);
@@ -1283,6 +1387,15 @@
         }
     }
 
+    private static void writeChecksumsToSession(PackageInstaller.Session session, String name,
+            Checksum[] checksums) throws IOException {
+        try (DataOutputStream dos = new DataOutputStream(session.openWrite(name, 0, -1))) {
+            for (Checksum checksum : checksums) {
+                Checksum.writeToStream(dos, checksum);
+            }
+        }
+    }
+
     private String uninstallPackageSilently(String packageName) throws IOException {
         return executeShellCommand("pm uninstall " + packageName);
     }
@@ -1294,6 +1407,16 @@
                 .anyMatch(line -> line.substring(prefixLength).equals(packageName));
     }
 
+    private String getAppCodePath(String packageName) throws IOException {
+        final String commandResult = executeShellCommand("pm dump " + packageName);
+        final String prefix = "    codePath=";
+        final int prefixLength = prefix.length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .filter(line -> line.startsWith(prefix))
+                .map(line -> line.substring(prefixLength))
+                .findFirst().get();
+    }
+
     @Nonnull
     private static String bytesToHexString(byte[] bytes) {
         return HexDump.toHexString(bytes, 0, bytes.length, /*upperCase=*/ false);
@@ -1326,7 +1449,7 @@
     private Certificate getInstallerCertificate() throws Exception {
         PackageManager pm = getPackageManager();
         PackageInfo installerPackageInfo = pm.getPackageInfo(CTS_PACKAGE_NAME,
-                GET_SIGNING_CERTIFICATES);
+                PackageManager.PackageInfoFlags.of(GET_SIGNING_CERTIFICATES));
         final List<Certificate> signatures = convertSignaturesToCertificates(
                 installerPackageInfo.signingInfo.getApkContentsSigners());
         return signatures.get(0);
diff --git a/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
index c937e2a..c74760b 100644
--- a/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ComponentInfoTest.java
@@ -16,6 +16,7 @@
 
 package android.content.pm.cts;
 
+import android.content.cts.R;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ComponentInfo;
 import android.content.pm.PackageManager;
@@ -30,8 +31,6 @@
 
 import com.android.compatibility.common.util.WidgetTestUtils;
 
-import android.content.cts.R;
-
 
 /**
  * Test {@link ComponentInfo}.
@@ -205,7 +204,8 @@
         assertEquals("name", mComponentInfo.loadLabel(pm));
 
         mComponentInfo.applicationInfo =
-                mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME, 0);
+                mContext.getPackageManager().getApplicationInfo(PACKAGE_NAME,
+                        PackageManager.ApplicationInfoFlags.of(0));
 
         mComponentInfo.nonLocalizedLabel = null;
         mComponentInfo.labelRes = R.string.hello_android;
diff --git a/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
index f24590b..07c2ca6 100644
--- a/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ConfigurationInfoTest.java
@@ -33,7 +33,7 @@
         // Test constructors
         new ConfigurationInfo();
         PackageInfo pkgInfo = pm.getPackageInfo(getContext().getPackageName(),
-                PackageManager.GET_CONFIGURATIONS);
+                PackageManager.PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS));
         ConfigurationInfo[] configInfoArray = pkgInfo.configPreferences;
         assertTrue(configInfoArray.length > 0);
         ConfigurationInfo configInfo = configInfoArray[0];
diff --git a/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java b/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
index da77b0e..92628db 100644
--- a/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/FeatureGroupInfoTest.java
@@ -56,7 +56,7 @@
         };
 
         PackageInfo pi = mPackageManager.getPackageInfo(getContext().getPackageName(),
-                PackageManager.GET_CONFIGURATIONS);
+                PackageManager.PackageInfoFlags.of(PackageManager.GET_CONFIGURATIONS));
         assertNotNull(pi);
         assertNotNull(pi.reqFeatures);
         assertNotNull(pi.featureGroups);
diff --git a/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java b/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
index 2548e86..4bc7a40 100644
--- a/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/InstantAppTest.java
@@ -17,8 +17,6 @@
 package android.content.pm.cts;
 
 
-import android.content.cts.MockActivity;
-
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
@@ -32,7 +30,6 @@
 
 import java.io.File;
 import java.util.List;
-import java.util.Set;
 
 /**
  * Test instant apps.
@@ -57,7 +54,8 @@
                 | MATCH_DIRECT_BOOT_UNAWARE
                 | MATCH_SYSTEM_ONLY;
         final List<ResolveInfo> matches =
-                mPackageManager.queryIntentServices(resolverIntent, resolveFlags);
+                mPackageManager.queryIntentServices(resolverIntent,
+                        PackageManager.ResolveInfoFlags.of(resolveFlags));
         assertTrue(matches == null || matches.size() <= 1);
     }
 
@@ -71,7 +69,8 @@
                 | MATCH_DIRECT_BOOT_UNAWARE
                 | MATCH_SYSTEM_ONLY;
         final List<ResolveInfo> matches =
-                mPackageManager.queryIntentActivities(intent, resolveFlags);
+                mPackageManager.queryIntentActivities(intent,
+                        PackageManager.ResolveInfoFlags.of(resolveFlags));
         assertTrue(matches == null || matches.size() <= 1);
     }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
index facc981..fd1b5d2 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageInfoTest.java
@@ -43,12 +43,16 @@
     protected void setUp() throws Exception {
         super.setUp();
         mPackageManager = getContext().getPackageManager();
-        mPackageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME, PackageManager.GET_ACTIVITIES
-                | PackageManager.GET_GIDS | PackageManager.GET_CONFIGURATIONS
-                | PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PERMISSIONS
-                | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
-                | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
-                | PackageManager.GET_SIGNATURES | PackageManager.GET_UNINSTALLED_PACKAGES);
+        mPackageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(
+                        PackageManager.GET_ACTIVITIES | PackageManager.GET_GIDS
+                                | PackageManager.GET_CONFIGURATIONS
+                                | PackageManager.GET_INSTRUMENTATION
+                                | PackageManager.GET_PERMISSIONS
+                                | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
+                                | PackageManager.GET_SERVICES | PackageManager.GET_ATTRIBUTIONS
+                                | PackageManager.GET_SIGNATURES
+                                | PackageManager.GET_UNINSTALLED_PACKAGES));
     }
 
     public void testPackageInfoOp() {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java b/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
index c5d5a04..8131b3e 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageItemInfoIconTest.java
@@ -75,7 +75,8 @@
         // size_48x48 is defined as the application icon in this test's AndroidManifest.xml
         Drawable expectedIcon = mContext.getDrawable(R.drawable.size_48x48);
 
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         PackageItemInfo itemInfo = new PackageItemInfo();
         itemInfo.icon = 0;
 
@@ -89,7 +90,8 @@
         // start is defined as the Activity icon in this test's AndroidManifest.xml
         Drawable expectedIcon = mContext.getDrawable(R.drawable.start);
 
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         PackageItemInfo itemInfo = getTestItemInfo();
 
         assertEquals(R.drawable.start, itemInfo.icon);
@@ -117,7 +119,8 @@
         // size_48x48 is defined as the app icon in this test's AndroidManifest.xml
         Drawable expectedIcon = mContext.getDrawable(R.drawable.size_48x48);
 
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         Drawable icon = appInfo.loadUnbadgedIcon(mPackageManager);
 
@@ -140,7 +143,8 @@
 
     private PackageItemInfo getTestItemInfo() throws PackageManager.NameNotFoundException {
         ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
-        return mPackageManager.getActivityInfo(componentName, 0);
+        return mPackageManager.getActivityInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(0));
     }
 
     private boolean comparePixelData(Drawable one, Drawable two) {
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 37a8f76..3d4d61b 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -78,6 +78,7 @@
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Function;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -104,6 +105,16 @@
     private static final String TEST_APK_SPLIT2_IDSIG = "HelloWorld5_xhdpi-v4.apk.idsig";
     private static final String TEST_APK_MALFORMED = "malformed.apk";
 
+    private static final String TEST_HW7 = "HelloWorld7.apk";
+    private static final String TEST_HW7_IDSIG = "HelloWorld7.apk.idsig";
+    private static final String TEST_HW7_SPLIT0 = "HelloWorld7_hdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT0_IDSIG = "HelloWorld7_hdpi-v4.apk.idsig";
+    private static final String TEST_HW7_SPLIT1 = "HelloWorld7_mdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT1_IDSIG = "HelloWorld7_mdpi-v4.apk.idsig";
+    private static final String TEST_HW7_SPLIT2 = "HelloWorld7_xhdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
+    private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
+
     private static final long EXPECTED_READ_TIME = 1000L;
 
     private IncrementalInstallSession mSession = null;
@@ -202,6 +213,8 @@
         final long blockSize = Os.statvfs("/data/incremental").f_bsize;
         final long preAllocatedBlocks = Os.statvfs("/data/incremental").f_bfree;
 
+        final AtomicLong freeSpaceDifference = new AtomicLong(-1L);
+
         mSession =
                 new IncrementalInstallSession.Builder()
                         .addApk(Paths.get(apk), Paths.get(idsig))
@@ -216,10 +229,8 @@
                             try {
                                 final long postAllocatedBlocks =
                                         Os.statvfs("/data/incremental").f_bfree;
-                                final long freeSpaceDifference =
-                                        (preAllocatedBlocks - postAllocatedBlocks) * blockSize;
-                                assertTrue(freeSpaceDifference
-                                        >= ((appFileSize * 1.015) + blockSize * 8));
+                                freeSpaceDifference.set(
+                                        (preAllocatedBlocks - postAllocatedBlocks) * blockSize);
                             } catch (Exception e) {
                                 Log.i(TAG, "ErrnoException: ", e);
                                 throw new AssertionError(e);
@@ -239,6 +250,10 @@
 
         assertTrue(isAppInstalled(TEST_APP_PACKAGE));
 
+        final double freeSpaceExpectedDifference = ((appFileSize * 1.015) + blockSize * 8);
+        assertTrue(freeSpaceDifference.get() + " >= " + freeSpaceExpectedDifference,
+                freeSpaceDifference.get() >= freeSpaceExpectedDifference);
+
         String installPath = executeShellCommand(String.format("pm path %s", TEST_APP_PACKAGE))
                                         .replaceFirst("package:", "")
                                         .trim();
@@ -745,12 +760,12 @@
 
         mSession =
                 new IncrementalInstallSession.Builder()
-                        .addApk(Paths.get(createApkPath(TEST_APK)),
-                                Paths.get(createApkPath(TEST_APK_IDSIG)))
-                        .addApk(Paths.get(createApkPath(TEST_APK_SPLIT0)),
-                                Paths.get(createApkPath(TEST_APK_SPLIT0_IDSIG)))
-                        .addApk(Paths.get(createApkPath(TEST_APK_SPLIT1)),
-                                Paths.get(createApkPath(TEST_APK_SPLIT1_IDSIG)))
+                        .addApk(Paths.get(createApkPath(TEST_HW7)),
+                                Paths.get(createApkPath(TEST_HW7_IDSIG)))
+                        .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT0)),
+                                Paths.get(createApkPath(TEST_HW7_SPLIT0_IDSIG)))
+                        .addApk(Paths.get(createApkPath(TEST_HW7_SPLIT1)),
+                                Paths.get(createApkPath(TEST_HW7_SPLIT1_IDSIG)))
                         .addExtraArgs("-t", "-i", CTS_PACKAGE_NAME)
                         .setLogger(new IncrementalDeviceConnection.Logger())
                         .build();
@@ -778,6 +793,9 @@
             final File apkToRead = getSplit("split_config.mdpi.apk");
             final long readTime0 = readAndReportTime(apkToRead, 1000);
 
+            if (readTime0 < EXPECTED_READ_TIME) {
+                executeShellCommand("atrace --async-dump");
+            }
             assertTrue(
                     "Must take longer than " + EXPECTED_READ_TIME + "ms: time0=" + readTime0 + "ms",
                     readTime0 >= EXPECTED_READ_TIME);
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index 3d967d7..cbbe421 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -16,28 +16,60 @@
 
 package android.content.pm.cts;
 
+import static android.content.Context.RECEIVER_EXPORTED;
+import static android.content.pm.Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256;
+import static android.content.pm.Checksum.TYPE_WHOLE_MERKLE_ROOT_4K_SHA256;
 import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_INCREMENTAL;
 import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_NONE;
 import static android.content.pm.PackageInstaller.DATA_LOADER_TYPE_STREAMING;
+import static android.content.pm.PackageInstaller.EXTRA_DATA_LOADER_TYPE;
+import static android.content.pm.PackageInstaller.EXTRA_SESSION_ID;
 import static android.content.pm.PackageInstaller.LOCATION_DATA_APP;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
+import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ROOT_HASH;
+import static android.content.pm.PackageManager.GET_SHARED_LIBRARY_FILES;
+import static android.content.pm.PackageManager.GET_SIGNING_CERTIFICATES;
+import static android.content.pm.PackageManager.MATCH_STATIC_SHARED_AND_SDK_LIBRARIES;
+import static android.content.pm.PackageManager.VERIFICATION_ALLOW;
+import static android.content.pm.PackageManager.VERIFICATION_REJECT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
 import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApkChecksum;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderParams;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
 import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
+import android.os.ConditionVariable;
 import android.os.ParcelFileDescriptor;
+import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
+import android.util.PackageUtils;
 
 import androidx.test.InstrumentationRegistry;
 
+import com.android.internal.util.ConcurrentUtils;
+import com.android.internal.util.HexDump;
+
+import libcore.util.HexEncoding;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -54,9 +86,15 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.cert.CertificateEncodingException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Optional;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 @RunWith(Parameterized.class)
@@ -64,6 +102,8 @@
 public class PackageManagerShellCommandTest {
     private static final String TEST_APP_PACKAGE = "com.example.helloworld";
 
+    private static final String CTS_PACKAGE_NAME = "android.content.cts";
+
     private static final String TEST_APK_PATH = "/data/local/tmp/cts/content/";
     private static final String TEST_HW5 = "HelloWorld5.apk";
     private static final String TEST_HW5_SPLIT0 = "HelloWorld5_hdpi-v4.apk";
@@ -78,6 +118,35 @@
     private static final String TEST_HW7_SPLIT3 = "HelloWorld7_xxhdpi-v4.apk";
     private static final String TEST_HW7_SPLIT4 = "HelloWorld7_xxxhdpi-v4.apk";
 
+    private static final String TEST_SDK1_PACKAGE = "com.test.sdk1_1";
+    private static final String TEST_SDK1_MAJOR_VERSION2_PACKAGE = "com.test.sdk1_2";
+    private static final String TEST_SDK2_PACKAGE = "com.test.sdk2_2";
+    private static final String TEST_SDK3_PACKAGE = "com.test.sdk3_3";
+    private static final String TEST_SDK_USER_PACKAGE = "com.test.sdk.user";
+
+    private static final String TEST_SDK1_NAME = "com.test.sdk1";
+    private static final String TEST_SDK2_NAME = "com.test.sdk2";
+    private static final String TEST_SDK3_NAME = "com.test.sdk3";
+
+    private static final String TEST_SDK1 = "HelloWorldSdk1.apk";
+    private static final String TEST_SDK1_UPDATED = "HelloWorldSdk1Updated.apk";
+    private static final String TEST_SDK1_MAJOR_VERSION2 = "HelloWorldSdk1MajorVersion2.apk";
+    private static final String TEST_SDK1_DIFFERENT_SIGNER = "HelloWorldSdk1DifferentSigner.apk";
+    private static final String TEST_SDK2 = "HelloWorldSdk2.apk";
+    private static final String TEST_SDK2_UPDATED = "HelloWorldSdk2Updated.apk";
+    private static final String TEST_USING_SDK1 = "HelloWorldUsingSdk1.apk";
+    private static final String TEST_USING_SDK1_AND_SDK2 = "HelloWorldUsingSdk1And2.apk";
+
+    private static final String TEST_SDK3_USING_SDK1 = "HelloWorldSdk3UsingSdk1.apk";
+    private static final String TEST_SDK3_USING_SDK1_AND_SDK2 = "HelloWorldSdk3UsingSdk1And2.apk";
+    private static final String TEST_USING_SDK3 = "HelloWorldUsingSdk3.apk";
+
+    private static final String TEST_HW_NO_APP_STORAGE = "HelloWorldNoAppStorage.apk";
+
+    private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
+
+    private static final long DEFAULT_STREAMING_VERIFICATION_TIMEOUT = 3 * 1000;
+
     @Rule
     public AbandonAllPackageSessionsRule mAbandonSessionsRule = new AbandonAllPackageSessionsRule();
 
@@ -94,9 +163,20 @@
     private boolean mIncremental = false;
     private String mInstall = "";
     private String mPackageVerifier = null;
+    private String mUnusedStaticSharedLibsMinCachePeriod = null;
+    private long mStreamingVerificationTimeoutMs = DEFAULT_STREAMING_VERIFICATION_TIMEOUT;
+    private int mSecondUser = -1;
 
     private static PackageInstaller getPackageInstaller() {
-        return InstrumentationRegistry.getContext().getPackageManager().getPackageInstaller();
+        return getPackageManager().getPackageInstaller();
+    }
+
+    private static PackageManager getPackageManager() {
+        return InstrumentationRegistry.getContext().getPackageManager();
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
     }
 
     private static UiAutomation getUiAutomation() {
@@ -170,9 +250,25 @@
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
 
-        // Disable the package verifier to avoid the dialog when installing an app.
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+        uninstallPackageSilently(TEST_SDK3_PACKAGE);
+        uninstallPackageSilently(TEST_SDK2_PACKAGE);
+        uninstallPackageSilently(TEST_SDK1_PACKAGE);
+        uninstallPackageSilently(TEST_SDK1_MAJOR_VERSION2_PACKAGE);
+
         mPackageVerifier = executeShellCommand("settings get global verifier_verify_adb_installs");
+        // Disable the package verifier for non-incremental installations to avoid the dialog
+        // when installing an app.
         executeShellCommand("settings put global verifier_verify_adb_installs 0");
+
+        mUnusedStaticSharedLibsMinCachePeriod = executeShellCommand(
+                "settings get global unused_static_shared_lib_min_cache_period");
+
+        try {
+            mStreamingVerificationTimeoutMs = Long.parseUnsignedLong(
+                    executeShellCommand("settings get global streaming_verifier_timeout"));
+        } catch (NumberFormatException ignore) {
+        }
     }
 
     @After
@@ -181,8 +277,26 @@
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
 
-        // Reset the package verifier setting to its original value.
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+        uninstallPackageSilently(TEST_SDK3_PACKAGE);
+        uninstallPackageSilently(TEST_SDK2_PACKAGE);
+        uninstallPackageSilently(TEST_SDK1_PACKAGE);
+        uninstallPackageSilently(TEST_SDK1_MAJOR_VERSION2_PACKAGE);
+
+        // Reset the global settings to their original values.
         executeShellCommand("settings put global verifier_verify_adb_installs " + mPackageVerifier);
+        executeShellCommand("settings put global unused_static_shared_lib_min_cache_period "
+                + mUnusedStaticSharedLibsMinCachePeriod);
+
+        // Set the test override to invalid.
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest", "invalid");
+        setSystemProperty("debug.pm.prune_unused_shared_libraries_delay", "invalid");
+        setSystemProperty("debug.pm.adb_verifier_override_package", "invalid");
+
+        if (mSecondUser != -1) {
+            stopUser(mSecondUser);
+            removeUser(mSecondUser);
+        }
     }
 
     private boolean checkIncrementalDeliveryFeature() {
@@ -575,6 +689,835 @@
         }
     }
 
+    @Test
+    public void testSdkInstallAndUpdate() throws Exception {
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        // Same APK.
+        installPackage(TEST_SDK1);
+
+        // Updated APK.
+        installPackage(TEST_SDK1_UPDATED);
+
+        // Reverted APK.
+        installPackage(TEST_SDK1);
+
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+    }
+
+    @Test
+    public void testSdkInstallMultipleMajorVersions() throws Exception {
+        // Major version 1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        // Major version 2.
+        installPackage(TEST_SDK1_MAJOR_VERSION2);
+
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 2));
+    }
+
+    @Test
+    public void testSdkInstallMultipleMinorVersionsWrongSignature() throws Exception {
+        // Major version 1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        // Major version 1, different signer.
+        installPackage(TEST_SDK1_DIFFERENT_SIGNER,
+                "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.test.sdk1_1 signatures "
+                        + "do not match previously installed version");
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+    }
+
+    @Test
+    public void testSdkInstallMultipleMajorVersionsWrongSignature() throws Exception {
+        // Major version 1.
+        installPackage(TEST_SDK1_DIFFERENT_SIGNER);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        // Major version 2.
+        installPackage(TEST_SDK1_MAJOR_VERSION2,
+                "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Package com.test.sdk1_1 signatures "
+                        + "do not match previously installed version");
+
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+    }
+
+    @Test
+    public void testSdkInstallAndUpdateTwoMajorVersions() throws Exception {
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        installPackage(TEST_SDK2);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Same APK.
+        installPackage(TEST_SDK1);
+        installPackage(TEST_SDK2);
+
+        // Updated APK.
+        installPackage(TEST_SDK1_UPDATED);
+        installPackage(TEST_SDK2_UPDATED);
+
+        // Reverted APK.
+        installPackage(TEST_SDK1);
+        installPackage(TEST_SDK2);
+
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+    }
+
+    @Test
+    public void testAppUsingSdkInstallAndUpdate() throws Exception {
+        // Try to install without required SDK1.
+        installPackage(TEST_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+        assertFalse(isAppInstalled(TEST_SDK_USER_PACKAGE));
+
+        // Now install the required SDK1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+        // Install and uninstall.
+        installPackage(TEST_USING_SDK1);
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+        // Update SDK1.
+        installPackage(TEST_SDK1_UPDATED);
+
+        // Install again.
+        installPackage(TEST_USING_SDK1);
+
+        // Check resolution API.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+                    PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+            assertEquals(1, appInfo.sharedLibraryInfos.size());
+            SharedLibraryInfo libInfo = appInfo.sharedLibraryInfos.get(0);
+            assertEquals("com.test.sdk1", libInfo.getName());
+            assertEquals(1, libInfo.getLongVersion());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        // Try to install without required SDK2.
+        installPackage(TEST_USING_SDK1_AND_SDK2, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+        // Now install the required SDK2.
+        installPackage(TEST_SDK2);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Install and uninstall.
+        installPackage(TEST_USING_SDK1_AND_SDK2);
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+        // Update both SDKs.
+        installPackage(TEST_SDK1_UPDATED);
+        installPackage(TEST_SDK2_UPDATED);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Install again.
+        installPackage(TEST_USING_SDK1_AND_SDK2);
+
+        // Check resolution API.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+                    PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+            assertEquals(2, appInfo.sharedLibraryInfos.size());
+            assertEquals("com.test.sdk1", appInfo.sharedLibraryInfos.get(0).getName());
+            assertEquals(1, appInfo.sharedLibraryInfos.get(0).getLongVersion());
+            assertEquals("com.test.sdk2", appInfo.sharedLibraryInfos.get(1).getName());
+            assertEquals(2, appInfo.sharedLibraryInfos.get(1).getLongVersion());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testInstallFailsMismatchingCertificate() throws Exception {
+        // Install the required SDK1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        // Try to install the package with empty digest.
+        installPackage(TEST_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+    }
+
+    @Test
+    public void testUninstallSdkWhileAppUsing() throws Exception {
+        // Install the required SDK1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+        // Install the package.
+        installPackage(TEST_USING_SDK1);
+
+        uninstallPackage(TEST_SDK1_PACKAGE, "Failure [DELETE_FAILED_USED_SHARED_LIBRARY]");
+    }
+
+    @Test
+    public void testGetSharedLibraries() throws Exception {
+        // Install the SDK1.
+        installPackage(TEST_SDK1);
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+            assertNotNull(sdk1);
+            SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+            assertNull(sdk2);
+        }
+
+        // Install the SDK2.
+        installPackage(TEST_SDK2);
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+            assertNotNull(sdk1);
+            SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+            assertNotNull(sdk2);
+        }
+
+        // Install and uninstall the user package.
+        {
+            setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                    getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+            installPackage(TEST_USING_SDK1_AND_SDK2);
+
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+            assertNotNull(sdk1);
+            SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+            assertNotNull(sdk2);
+
+            assertEquals(TEST_SDK_USER_PACKAGE,
+                    sdk1.getDependentPackages().get(0).getPackageName());
+            assertEquals(TEST_SDK_USER_PACKAGE,
+                    sdk2.getDependentPackages().get(0).getPackageName());
+
+            uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+        }
+
+        // Uninstall the SDK1.
+        uninstallPackageSilently(TEST_SDK1_PACKAGE);
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+            assertNull(sdk1);
+            SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+            assertNotNull(sdk2);
+        }
+
+        // Uninstall the SDK2.
+        uninstallPackageSilently(TEST_SDK2_PACKAGE);
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk1 = findLibrary(libs, "com.test.sdk1", 1);
+            assertNull(sdk1);
+            SharedLibraryInfo sdk2 = findLibrary(libs, "com.test.sdk2", 2);
+            assertNull(sdk2);
+        }
+    }
+
+    @Test
+    public void testUninstallUnusedSdks() throws Exception {
+        installPackage(TEST_SDK1);
+        installPackage(TEST_SDK2);
+
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                getPackageCertDigest(TEST_SDK1_PACKAGE));
+        installPackage(TEST_USING_SDK1_AND_SDK2);
+
+        setSystemProperty("debug.pm.prune_unused_shared_libraries_delay", "0");
+        executeShellCommand("settings put global unused_static_shared_lib_min_cache_period 0");
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+        // Wait for 3secs max.
+        for (int i = 0; i < 30; ++i) {
+            if (!isSdkInstalled(TEST_SDK1_NAME, 1) && !isSdkInstalled(TEST_SDK2_NAME, 2)) {
+                break;
+            }
+            final int beforeRetryDelayMs = 100;
+            Thread.currentThread().sleep(beforeRetryDelayMs);
+        }
+        assertFalse(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertFalse(isSdkInstalled(TEST_SDK2_NAME, 2));
+    }
+
+    @Test
+    public void testAppUsingSdkUsingSdkInstallAndUpdate() throws Exception {
+        // Try to install without required SDK1.
+        installPackage(TEST_USING_SDK3, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+        assertFalse(isAppInstalled(TEST_SDK_USER_PACKAGE));
+
+        // Try to install SDK3 without required SDK1.
+        installPackage(TEST_SDK3_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+        assertFalse(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+        // Now install the required SDK1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+        // Now install the required SDK3.
+        installPackage(TEST_SDK3_USING_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+        // Install and uninstall.
+        installPackage(TEST_USING_SDK3);
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+        // Update SDK1.
+        installPackage(TEST_SDK1_UPDATED);
+
+        // Install again.
+        installPackage(TEST_USING_SDK3);
+
+        // Check resolution API.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+                    PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+            assertEquals(1, appInfo.sharedLibraryInfos.size());
+            SharedLibraryInfo libInfo = appInfo.sharedLibraryInfos.get(0);
+            assertEquals("com.test.sdk3", libInfo.getName());
+            assertEquals(3, libInfo.getLongVersion());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+
+        // Try to install updated SDK3 without required SDK2.
+        installPackage(TEST_SDK3_USING_SDK1_AND_SDK2,
+                "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+        // Now install the required SDK2.
+        installPackage(TEST_SDK2);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+        assertTrue(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+        // Install and uninstall.
+        installPackage(TEST_USING_SDK3);
+        uninstallPackageSilently(TEST_SDK_USER_PACKAGE);
+
+        // Update both SDKs.
+        installPackage(TEST_SDK1_UPDATED);
+        installPackage(TEST_SDK2_UPDATED);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Install again.
+        installPackage(TEST_USING_SDK3);
+
+        // Check resolution API.
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            ApplicationInfo appInfo = getPackageManager().getApplicationInfo(TEST_SDK_USER_PACKAGE,
+                    PackageManager.ApplicationInfoFlags.of(GET_SHARED_LIBRARY_FILES));
+            assertEquals(1, appInfo.sharedLibraryInfos.size());
+            assertEquals("com.test.sdk3", appInfo.sharedLibraryInfos.get(0).getName());
+            assertEquals(3, appInfo.sharedLibraryInfos.get(0).getLongVersion());
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testSdkUsingSdkInstallAndUpdate() throws Exception {
+        // Try to install without required SDK1.
+        installPackage(TEST_SDK3_USING_SDK1, "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+        assertFalse(isSdkInstalled(TEST_SDK3_NAME, 3));
+
+        // Now install the required SDK1.
+        installPackage(TEST_SDK1);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+
+        setSystemProperty("debug.pm.uses_sdk_library_default_cert_digest",
+                getPackageCertDigest(TEST_SDK1_PACKAGE));
+
+        // Install and uninstall.
+        installPackage(TEST_SDK3_USING_SDK1);
+        uninstallPackageSilently(TEST_SDK3_PACKAGE);
+
+        // Update SDK1.
+        installPackage(TEST_SDK1_UPDATED);
+
+        // Install again.
+        installPackage(TEST_SDK3_USING_SDK1);
+
+        // Check resolution API.
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk3 = findLibrary(libs, "com.test.sdk3", 3);
+            assertNotNull(sdk3);
+            List<SharedLibraryInfo> deps = sdk3.getDependencies();
+            assertEquals(1, deps.size());
+            SharedLibraryInfo libInfo = deps.get(0);
+            assertEquals("com.test.sdk1", libInfo.getName());
+            assertEquals(1, libInfo.getLongVersion());
+        }
+
+        // Try to install without required SDK2.
+        installPackage(TEST_SDK3_USING_SDK1_AND_SDK2,
+                "Failure [INSTALL_FAILED_MISSING_SHARED_LIBRARY");
+
+        // Now install the required SDK2.
+        installPackage(TEST_SDK2);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Install and uninstall.
+        installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+        uninstallPackageSilently(TEST_SDK3_PACKAGE);
+
+        // Update both SDKs.
+        installPackage(TEST_SDK1_UPDATED);
+        installPackage(TEST_SDK2_UPDATED);
+        assertTrue(isSdkInstalled(TEST_SDK1_NAME, 1));
+        assertTrue(isSdkInstalled(TEST_SDK2_NAME, 2));
+
+        // Install again.
+        installPackage(TEST_SDK3_USING_SDK1_AND_SDK2);
+
+        // Check resolution API.
+        {
+            List<SharedLibraryInfo> libs = getSharedLibraries();
+            SharedLibraryInfo sdk3 = findLibrary(libs, "com.test.sdk3", 3);
+            assertNotNull(sdk3);
+            List<SharedLibraryInfo> deps = sdk3.getDependencies();
+            assertEquals(2, deps.size());
+            assertEquals("com.test.sdk1", deps.get(0).getName());
+            assertEquals(1, deps.get(0).getLongVersion());
+            assertEquals("com.test.sdk2", deps.get(1).getName());
+            assertEquals(2, deps.get(1).getLongVersion());
+        }
+    }
+
+    private void runPackageVerifierTest(BiConsumer<Context, Intent> onBroadcast)
+            throws Exception {
+        runPackageVerifierTest("Success", onBroadcast);
+    }
+
+    private void runPackageVerifierTest(String expectedResultStartsWith,
+            BiConsumer<Context, Intent> onBroadcast) throws Exception {
+        AtomicReference<Thread> onBroadcastThread = new AtomicReference<>();
+
+        runPackageVerifierTestSync(expectedResultStartsWith, (context, intent) -> {
+            Thread thread = new Thread(() -> onBroadcast.accept(context, intent));
+            thread.start();
+            onBroadcastThread.set(thread);
+        });
+
+        onBroadcastThread.get().join();
+    }
+
+    private void runPackageVerifierTestSync(String expectedResultStartsWith,
+            BiConsumer<Context, Intent> onBroadcast) throws Exception {
+        // Install a package.
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+
+        getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.PACKAGE_VERIFICATION_AGENT);
+
+        // Create a single-use broadcast receiver
+        BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                context.unregisterReceiver(this);
+                onBroadcast.accept(context, intent);
+            }
+        };
+        // Create an intent-filter and register the receiver
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_NEEDS_VERIFICATION);
+        intentFilter.addDataType(PACKAGE_MIME_TYPE);
+        getContext().registerReceiver(broadcastReceiver, intentFilter, RECEIVER_EXPORTED);
+
+        // Enable verification.
+        executeShellCommand("settings put global verifier_verify_adb_installs 1");
+        // Override verifier for updates of debuggable apps.
+        setSystemProperty("debug.pm.adb_verifier_override_package", CTS_PACKAGE_NAME);
+
+        // Update the package, should trigger verifier override.
+        installPackage(TEST_HW7, expectedResultStartsWith);
+    }
+
+    @Test
+    public void testPackageVerifierAllow() throws Exception {
+        AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+        runPackageVerifierTest((context, intent) -> {
+            int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+            assertNotEquals(-1, verificationId);
+
+            dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+            int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+            assertNotEquals(-1, sessionId);
+
+            getPackageManager().verifyPendingInstall(verificationId, VERIFICATION_ALLOW);
+        });
+
+        assertEquals(mDataLoaderType, dataLoaderType.get());
+    }
+
+    @Test
+    public void testPackageVerifierReject() throws Exception {
+        AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+        runPackageVerifierTest("Failure [INSTALL_FAILED_VERIFICATION_FAILURE: Install not allowed]",
+                (context, intent) -> {
+                    int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+                    assertNotEquals(-1, verificationId);
+
+                    dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+                    int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+                    assertNotEquals(-1, sessionId);
+
+                    getPackageManager().verifyPendingInstall(verificationId, VERIFICATION_REJECT);
+                });
+
+        assertEquals(mDataLoaderType, dataLoaderType.get());
+    }
+
+    @Test
+    public void testPackageVerifierRejectAfterTimeout() throws Exception {
+        AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+        runPackageVerifierTestSync("Success", (context, intent) -> {
+            int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+            assertNotEquals(-1, verificationId);
+
+            dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+            int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+            assertNotEquals(-1, sessionId);
+
+            try {
+                if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+                    // For streaming installations, the timeout is fixed at 3secs and always
+                    // allow the install. Try to extend the timeout and then reject after
+                    // much shorter time.
+                    getPackageManager().extendVerificationTimeout(verificationId,
+                            VERIFICATION_REJECT, mStreamingVerificationTimeoutMs * 3);
+                    Thread.sleep(mStreamingVerificationTimeoutMs * 2);
+                    getPackageManager().verifyPendingInstall(verificationId,
+                            VERIFICATION_REJECT);
+                } else {
+                    getPackageManager().verifyPendingInstall(verificationId,
+                            VERIFICATION_ALLOW);
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        assertEquals(mDataLoaderType, dataLoaderType.get());
+    }
+
+    @Test
+    public void testPackageVerifierWithExtensionAndTimeout() throws Exception {
+        AtomicInteger dataLoaderType = new AtomicInteger(-1);
+
+        runPackageVerifierTest((context, intent) -> {
+            int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+            assertNotEquals(-1, verificationId);
+
+            dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+            int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+            assertNotEquals(-1, sessionId);
+
+            try {
+                if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+                    // For streaming installations, the timeout is fixed at 3secs and always
+                    // allow the install. Try to extend the timeout and then reject after
+                    // much shorter time.
+                    getPackageManager().extendVerificationTimeout(verificationId,
+                            VERIFICATION_REJECT, mStreamingVerificationTimeoutMs * 3);
+                    Thread.sleep(mStreamingVerificationTimeoutMs * 2);
+                    getPackageManager().verifyPendingInstall(verificationId,
+                            VERIFICATION_REJECT);
+                } else {
+                    getPackageManager().verifyPendingInstall(verificationId,
+                            VERIFICATION_ALLOW);
+                }
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        assertEquals(mDataLoaderType, dataLoaderType.get());
+    }
+
+    @Test
+    public void testPackageVerifierWithChecksums() throws Exception {
+        AtomicInteger dataLoaderType = new AtomicInteger(-1);
+        List<ApkChecksum> checksums = new ArrayList<>();
+        StringBuilder rootHash = new StringBuilder();
+
+        runPackageVerifierTest((context, intent) -> {
+            int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
+            assertNotEquals(-1, verificationId);
+
+            dataLoaderType.set(intent.getIntExtra(EXTRA_DATA_LOADER_TYPE, -1));
+            int sessionId = intent.getIntExtra(EXTRA_SESSION_ID, -1);
+            assertNotEquals(-1, sessionId);
+
+            try {
+                PackageInstaller.Session session = getPackageInstaller().openSession(sessionId);
+                assertNotNull(session);
+
+                rootHash.append(intent.getStringExtra(EXTRA_VERIFICATION_ROOT_HASH));
+
+                String[] names = session.getNames();
+                assertEquals(1, names.length);
+                session.requestChecksums(names[0], 0, PackageManager.TRUST_ALL,
+                        ConcurrentUtils.DIRECT_EXECUTOR,
+                        result -> checksums.addAll(result));
+            } catch (IOException | CertificateEncodingException e) {
+                throw new RuntimeException(e);
+            }
+        });
+
+        assertEquals(mDataLoaderType, dataLoaderType.get());
+
+        assertEquals(1, checksums.size());
+
+        if (mDataLoaderType == DATA_LOADER_TYPE_INCREMENTAL) {
+            assertEquals(TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, checksums.get(0).getType());
+            assertEquals(rootHash.toString(),
+                    "base.apk:" + HexDump.toHexString(checksums.get(0).getValue()));
+        } else {
+            assertEquals(TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256, checksums.get(0).getType());
+        }
+    }
+
+    @Test
+    public void testGetFirstInstallTime() throws Exception {
+        final int currentUser = getContext().getUserId();
+        final long startTimeMillisForCurrentUser = System.currentTimeMillis();
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+        final long origFirstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+                TEST_APP_PACKAGE, currentUser);
+        // Validate the timestamp
+        assertTrue(origFirstInstallTimeForCurrentUser > 0);
+        assertTrue(startTimeMillisForCurrentUser < origFirstInstallTimeForCurrentUser);
+        assertTrue(System.currentTimeMillis() > origFirstInstallTimeForCurrentUser);
+
+        // Install again with replace and the firstInstallTime should remain the same
+        installPackage(TEST_HW5);
+        long firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+                TEST_APP_PACKAGE, currentUser);
+        assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+
+        // Start another user and install this test itself for that user
+        mSecondUser = createUser("Another User");
+        assertTrue(startUser(mSecondUser));
+        long startTimeMillisForSecondUser = System.currentTimeMillis();
+        installExistingPackageAsUser(getContext().getPackageName(), mSecondUser);
+        assertTrue(isAppInstalledForUser(getContext().getPackageName(), mSecondUser));
+        // Install test package with replace
+        installPackageAsUser(TEST_HW5, mSecondUser);
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+        firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+                TEST_APP_PACKAGE, currentUser);
+        // firstInstallTime should remain unchanged for the current user
+        assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+
+        long firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(
+                TEST_APP_PACKAGE, mSecondUser);
+        // firstInstallTime for the other user should be different
+        assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+        assertTrue(startTimeMillisForSecondUser < firstInstallTimeForSecondUser);
+        assertTrue(System.currentTimeMillis() > firstInstallTimeForSecondUser);
+
+        // Uninstall for the other user
+        uninstallPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+        assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+        // Install test package as an existing package
+        startTimeMillisForSecondUser = System.currentTimeMillis();
+        installExistingPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+
+        firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(
+                TEST_APP_PACKAGE, currentUser);
+        // firstInstallTime still remains unchanged for the current user
+        assertEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+        firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, mSecondUser);
+        // firstInstallTime for the other user should be different
+        assertNotEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+        assertTrue(startTimeMillisForSecondUser < firstInstallTimeForSecondUser);
+        assertTrue(System.currentTimeMillis() > firstInstallTimeForSecondUser);
+
+        // Uninstall for all users
+        uninstallPackageSilently(TEST_APP_PACKAGE);
+        assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+        assertFalse(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+        // Reinstall for all users
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+        firstInstallTimeForCurrentUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, currentUser);
+        // First install time is now different because the package was fully uninstalled
+        assertNotEquals(origFirstInstallTimeForCurrentUser, firstInstallTimeForCurrentUser);
+        firstInstallTimeForSecondUser = getFirstInstallTimeAsUser(TEST_APP_PACKAGE, mSecondUser);
+        // Same firstInstallTime because package was installed for both users at the same time
+        assertEquals(firstInstallTimeForCurrentUser, firstInstallTimeForSecondUser);
+    }
+
+    private long getFirstInstallTimeAsUser(String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        final Context contextAsUser = getContext().createContextAsUser(UserHandle.of(userId), 0);
+        final PackageManager packageManager = contextAsUser.getPackageManager();
+        final PackageInfo packageInfo = packageManager.getPackageInfo(packageName,
+                PackageManager.PackageInfoFlags.of(0));
+        return packageInfo.firstInstallTime;
+    }
+
+    @Test
+    public void testAppWithNoAppStorageUpdateSuccess() throws Exception {
+        installPackage(TEST_HW_NO_APP_STORAGE);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        // Updates that don't change value of NO_APP_DATA_STORAGE property are allowed.
+        installPackage(TEST_HW_NO_APP_STORAGE);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testAppUpdateAddsNoAppDataStorageProperty() throws Exception {
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        installPackage(
+                TEST_HW_NO_APP_STORAGE,
+                "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Update "
+                        + "attempted to change value of "
+                        + "android.internal.PROPERTY_NO_APP_DATA_STORAGE");
+    }
+
+    @Test
+    public void testAppUpdateRemovesNoAppDataStorageProperty() throws Exception {
+        installPackage(TEST_HW_NO_APP_STORAGE);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        installPackage(
+                TEST_HW5,
+                "Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Update "
+                        + "attempted to change value of "
+                        + "android.internal.PROPERTY_NO_APP_DATA_STORAGE");
+    }
+
+    @Test
+    public void testNoAppDataStoragePropertyCanChangeAfterUninstall() throws Exception {
+        installPackage(TEST_HW_NO_APP_STORAGE);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+        uninstallPackageSilently(TEST_APP_PACKAGE);
+        // After app is uninstalled new install can change the value of the property.
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalled(TEST_APP_PACKAGE));
+    }
+
+    @Test
+    public void testPackageFullyRemovedBroadcastAfterUninstall() throws IOException {
+        final int currentUser = getContext().getUserId();
+        // Start another user and install this test itself for that user
+        mSecondUser = createUser("Another User");
+        assertTrue(startUser(mSecondUser));
+        installExistingPackageAsUser(getContext().getPackageName(), mSecondUser);
+        installPackage(TEST_HW5);
+        assertTrue(isAppInstalledForUser(getContext().getPackageName(), currentUser));
+        assertTrue(isAppInstalledForUser(getContext().getPackageName(), mSecondUser));
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, currentUser));
+        assertTrue(isAppInstalledForUser(TEST_APP_PACKAGE, mSecondUser));
+        final FullyRemovedBroadcastReceiver broadcastReceiverForCurrentUser =
+                new FullyRemovedBroadcastReceiver(TEST_APP_PACKAGE, currentUser);
+        final FullyRemovedBroadcastReceiver broadcastReceiverForSecondUser =
+                new FullyRemovedBroadcastReceiver(TEST_APP_PACKAGE, mSecondUser);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
+        intentFilter.addDataScheme("package");
+        getContext().registerReceiver(
+                broadcastReceiverForCurrentUser, intentFilter, RECEIVER_EXPORTED);
+        getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            getContext().createContextAsUser(UserHandle.of(mSecondUser), 0).registerReceiver(
+                    broadcastReceiverForSecondUser, intentFilter, RECEIVER_EXPORTED);
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+        // Verify that uninstall with "keep data" doesn't send the broadcast
+        uninstallPackageWithKeepData(TEST_APP_PACKAGE, mSecondUser);
+        assertFalse(broadcastReceiverForSecondUser.isBroadcastReceived());
+        installExistingPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+        // Verify that uninstall on a specific user only sends the broadcast to the user
+        uninstallPackageAsUser(TEST_APP_PACKAGE, mSecondUser);
+        assertTrue(broadcastReceiverForSecondUser.isBroadcastReceived());
+        assertFalse(broadcastReceiverForCurrentUser.isBroadcastReceived());
+        uninstallPackageSilently(TEST_APP_PACKAGE);
+        assertTrue(broadcastReceiverForCurrentUser.isBroadcastReceived());
+    }
+
+    private static class FullyRemovedBroadcastReceiver extends BroadcastReceiver {
+        private final String mTargetPackage;
+        private final int mTargetUserId;
+        private final ConditionVariable mUserReceivedBroadcast;
+        FullyRemovedBroadcastReceiver(String packageName, int targetUserId) {
+            mTargetPackage = packageName;
+            mTargetUserId = targetUserId;
+            mUserReceivedBroadcast = new ConditionVariable();
+            mUserReceivedBroadcast.close();
+        }
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            context.unregisterReceiver(this);
+            final String packageName = intent.getData().getEncodedSchemeSpecificPart();
+            final int userId = context.getUserId();
+            if (intent.getAction().equals(Intent.ACTION_PACKAGE_FULLY_REMOVED)
+                    && packageName.equals(mTargetPackage) && userId == mTargetUserId) {
+                mUserReceivedBroadcast.open();
+            }
+        }
+        public boolean isBroadcastReceived() {
+            return mUserReceivedBroadcast.block(2000);
+        }
+    }
+
+    private List<SharedLibraryInfo> getSharedLibraries() {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            return getPackageManager().getSharedLibraries(PackageManager.PackageInfoFlags.of(0));
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private SharedLibraryInfo findLibrary(List<SharedLibraryInfo> libs, String name, long version) {
+        for (int i = 0, size = libs.size(); i < size; ++i) {
+            SharedLibraryInfo lib = libs.get(i);
+            if (name.equals(lib.getName()) && version == lib.getLongVersion()) {
+                return lib;
+            }
+        }
+        return null;
+    }
+
     private String createUpdateSession(String packageName) throws IOException {
         return createSession("-p " + packageName);
     }
@@ -631,6 +1574,39 @@
                 .anyMatch(line -> line.substring(prefixLength).equals(packageName));
     }
 
+    private boolean isAppInstalledForUser(String packageName, int userId) throws IOException {
+        final String commandResult = executeShellCommand(
+                String.format("pm list packages --user %d %s", userId, packageName)
+        );
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.equals("package:" + packageName));
+    }
+
+    private boolean isSdkInstalled(String name, int versionMajor) throws IOException {
+        final String sdkString = name + ":" + versionMajor;
+        final String commandResult = executeShellCommand("pm list sdks");
+        final int prefixLength = "sdk:".length();
+        return Arrays.stream(commandResult.split("\\r?\\n"))
+                .anyMatch(line -> line.length() > prefixLength && line.substring(
+                        prefixLength).equals(sdkString));
+    }
+
+    private String getPackageCertDigest(String packageName) throws Exception {
+        getUiAutomation().adoptShellPermissionIdentity();
+        try {
+            PackageInfo sdkPackageInfo = getPackageManager().getPackageInfo(packageName,
+                    PackageManager.PackageInfoFlags.of(
+                            GET_SIGNING_CERTIFICATES | MATCH_STATIC_SHARED_AND_SDK_LIBRARIES));
+            SigningInfo signingInfo = sdkPackageInfo.signingInfo;
+            Signature[] signatures =
+                    signingInfo != null ? signingInfo.getSigningCertificateHistory() : null;
+            byte[] digest = PackageUtils.computeSha256DigestBytes(signatures[0].toByteArray());
+            return new String(HexEncoding.encode(digest));
+        } finally {
+            getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
     private String getSplits(String packageName) throws IOException {
         final String commandResult = executeShellCommand("pm dump " + packageName);
         final String prefix = "    splits=[";
@@ -648,12 +1624,34 @@
         return TEST_APK_PATH + baseName;
     }
 
+    /* Install for all the users */
     private void installPackage(String baseName) throws IOException {
         File file = new File(createApkPath(baseName));
         assertEquals("Success\n", executeShellCommand(
                 "pm " + mInstall + " -t -g " + file.getPath()));
     }
 
+    private void installPackage(String baseName, String expectedResultStartsWith)
+            throws IOException {
+        File file = new File(createApkPath(baseName));
+        String result = executeShellCommand("pm " + mInstall + " -t -g " + file.getPath());
+        assertTrue(result, result.startsWith(expectedResultStartsWith));
+    }
+
+    /* Install a package for a new user; this would replace the old package */
+    private void installPackageAsUser(String baseName, int userId) throws IOException {
+        File file = new File(createApkPath(baseName));
+        assertEquals("Success\n", executeShellCommand(
+                "pm " + mInstall + " -t -g --user " + userId + " " + file.getPath()));
+    }
+
+    /* Install an existing package for a new user */
+    private void installExistingPackageAsUser(String packageName, int userId) throws IOException {
+        String result = executeShellCommand(
+                String.format("pm install-existing --user %d %s", userId, packageName));
+        assertEquals("Package " + packageName + " installed for user: " + userId + "\n", result);
+    }
+
     private void updatePackage(String packageName, String baseName) throws IOException {
         File file = new File(createApkPath(baseName));
         assertEquals("Success\n", executeShellCommand(
@@ -730,10 +1728,25 @@
                         splits)));
     }
 
+    private void uninstallPackage(String packageName, String expectedResultStartsWith)
+            throws IOException {
+        String result = uninstallPackageSilently(packageName);
+        assertTrue(result, result.startsWith(expectedResultStartsWith));
+    }
+
     private String uninstallPackageSilently(String packageName) throws IOException {
         return executeShellCommand("pm uninstall " + packageName);
     }
 
+    /* Uninstall for one user */
+    private void uninstallPackageAsUser(String packageName, int userId) throws IOException {
+        executeShellCommand(String.format("pm uninstall --user %d %s", userId, packageName));
+    }
+
+    private void uninstallPackageWithKeepData(String packageName, int userId) throws IOException {
+        executeShellCommand(String.format("pm uninstall -k --user %d %s", userId, packageName));
+    }
+
     private void uninstallSplits(String packageName, String[] splitNames) throws IOException {
         for (String splitName : splitNames) {
             assertEquals("Success\n",
@@ -745,5 +1758,35 @@
         assertEquals("Success\n", executeShellCommand(
                 "pm uninstall " + packageName + " " + String.join(" ", splitNames)));
     }
+
+    private void setSystemProperty(String name, String value) throws Exception {
+        assertEquals("", executeShellCommand("setprop " + name + " " + value));
+    }
+
+    private int createUser(String name) throws IOException {
+        final String output = executeShellCommand("pm create-user " + name);
+        if (output.startsWith("Success")) {
+            return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+        }
+        throw new IllegalStateException(String.format("Failed to create user: %s", output));
+    }
+
+    private void removeUser(int userId) throws IOException {
+        executeShellCommand("pm remove-user " + userId);
+    }
+
+    private boolean startUser(int userId) throws IOException {
+        String cmd = "am start-user -w " + userId;
+        final String output = executeShellCommand(cmd);
+        if (output.startsWith("Error")) {
+            return false;
+        }
+        String state = executeShellCommand("am get-started-user-state " + userId);
+        return state.contains("RUNNING_UNLOCKED");
+    }
+
+    private void stopUser(int userId) throws IOException {
+        executeShellCommand("am stop-user -w -f " + userId);
+    }
 }
 
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 262c687..d1abe03 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -20,6 +20,10 @@
 import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
 import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
+import static android.content.pm.PackageManager.DONT_KILL_APP;
 import static android.content.pm.PackageManager.GET_ACTIVITIES;
 import static android.content.pm.PackageManager.GET_META_DATA;
 import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -48,10 +52,15 @@
 import static org.testng.Assert.assertThrows;
 
 import android.annotation.NonNull;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.ServiceConnection;
 import android.content.cts.MockActivity;
 import android.content.cts.MockContentProvider;
 import android.content.cts.MockReceiver;
@@ -64,18 +73,22 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ComponentEnabledSetting;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
+import android.net.Uri;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -83,10 +96,16 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.core.content.FileProvider;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.rule.ServiceTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.SystemUtil;
+import com.android.compatibility.common.util.TestUtils;
+
+import junit.framework.AssertionFailedError;
 
 import org.junit.After;
 import org.junit.Before;
@@ -95,6 +114,7 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -102,6 +122,10 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
@@ -117,13 +141,16 @@
 
     private Context mContext;
     private PackageManager mPackageManager;
+    private Instrumentation mInstrumentation;
     private static final String PACKAGE_NAME = "android.content.cts";
-    private static final String CONTENT_PKG_NAME = "android.content.cts";
+    private static final String STUB_PACKAGE_NAME = "com.android.cts.stub";
     private static final String APPLICATION_NAME = "android.content.cts.MockApplication";
     private static final String ACTIVITY_ACTION_NAME = "android.intent.action.PMTEST";
     private static final String MAIN_ACTION_NAME = "android.intent.action.MAIN";
     private static final String SERVICE_ACTION_NAME =
-                                "android.content.pm.cts.activity.PMTEST_SERVICE";
+            "android.content.pm.cts.activity.PMTEST_SERVICE";
+    private static final String RECEIVER_ACTION_NAME =
+            "android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER";
     private static final String GRANTED_PERMISSION_NAME = "android.permission.INTERNET";
     private static final String NOT_GRANTED_PERMISSION_NAME = "android.permission.HARDWARE_TEST";
     private static final String ACTIVITY_NAME = "android.content.pm.cts.TestPmActivity";
@@ -138,6 +165,7 @@
             "android.content.cts.permission.TEST_DYNAMIC";
     // Number of activities/activity-alias in AndroidManifest
     private static final int NUM_OF_ACTIVITIES_IN_MANIFEST = 12;
+    public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10);
 
     private static final String SHIM_APEX_PACKAGE_NAME = "com.android.apex.cts.shim";
 
@@ -146,6 +174,8 @@
             MATCH_APEX, MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS};
 
     private static final String SAMPLE_APK_BASE = "/data/local/tmp/cts/content/";
+    private static final String EMPTY_APP_APK = SAMPLE_APK_BASE
+            + "CtsContentEmptyTestApp.apk";
     private static final String LONG_PACKAGE_NAME_APK = SAMPLE_APK_BASE
             + "CtsContentLongPackageNameTestApp.apk";
     private static final String LONG_SHARED_USER_ID_APK = SAMPLE_APK_BASE
@@ -164,13 +194,48 @@
     private static final String SHELL_PACKAGE_NAME = "com.android.shell";
     private static final String HELLO_WORLD_PACKAGE_NAME = "com.example.helloworld";
     private static final String HELLO_WORLD_APK = SAMPLE_APK_BASE + "HelloWorld5.apk";
+    private static final String MOCK_LAUNCHER_PACKAGE_NAME = "android.content.cts.mocklauncherapp";
+    private static final String MOCK_LAUNCHER_APK = SAMPLE_APK_BASE
+            + "CtsContentMockLauncherTestApp.apk";
+    private static final String NON_EXISTENT_PACKAGE_NAME = "android.content.cts.nonexistent.pkg";
+    private static final String STUB_PACKAGE_APK = SAMPLE_APK_BASE
+            + "CtsSyncAccountAccessStubs.apk";
 
     private static final int MAX_SAFE_LABEL_LENGTH = 1000;
 
+    // For intent resolution tests
+    private static final String NON_EXISTENT_ACTION_NAME = "android.intent.action.cts.NON_EXISTENT";
+    private static final String INTENT_RESOLUTION_TEST_PKG_NAME =
+            "android.content.cts.IntentResolutionTest";
+    private static final String RESOLUTION_TEST_ACTION_NAME =
+            "android.intent.action.RESOLUTION_TEST";
+    private static final String SELECTOR_ACTION_NAME = "android.intent.action.SELECTORTEST";
+    private static final String FILE_PROVIDER_AUTHORITY = "android.content.cts.fileprovider";
+
+    private static final ComponentName ACTIVITY_COMPONENT = new ComponentName(
+            PACKAGE_NAME, ACTIVITY_NAME);
+    private static final ComponentName SERVICE_COMPONENT = new ComponentName(
+            PACKAGE_NAME, SERVICE_NAME);
+    private static final ComponentName STUB_ACTIVITY_COMPONENT = ComponentName.createRelative(
+            STUB_PACKAGE_NAME, ".StubActivity");
+    private static final ComponentName STUB_SERVICE_COMPONENT = ComponentName.createRelative(
+            STUB_PACKAGE_NAME, ".StubService");
+    private static final ComponentName RESET_ENABLED_SETTING_ACTIVITY_COMPONENT =
+            ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockActivity");
+    private static final ComponentName RESET_ENABLED_SETTING_RECEIVER_COMPONENT =
+            ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockReceiver");
+    private static final ComponentName RESET_ENABLED_SETTING_SERVICE_COMPONENT =
+            ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockService");
+    private static final ComponentName RESET_ENABLED_SETTING_PROVIDER_COMPONENT =
+            ComponentName.createRelative(MOCK_LAUNCHER_PACKAGE_NAME, ".MockProvider");
+
+    private final ServiceTestRule mServiceTestRule = new ServiceTestRule();
+
     @Before
     public void setup() throws Exception {
         mContext = InstrumentationRegistry.getContext();
         mPackageManager = mContext.getPackageManager();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
     }
 
     @After
@@ -178,6 +243,7 @@
         uninstallPackage(EMPTY_APP_PACKAGE_NAME);
         uninstallPackage(EMPTY_APP_MAX_PACKAGE_NAME);
         uninstallPackage(HELLO_WORLD_PACKAGE_NAME);
+        uninstallPackage(MOCK_LAUNCHER_PACKAGE_NAME);
     }
 
     @Test
@@ -188,17 +254,20 @@
         String cmpActivityName = "android.content.pm.cts.TestPmCompare";
         // List with different activities and the filter doesn't work,
         List<ResolveInfo> listWithDiff = mPackageManager.queryIntentActivityOptions(
-                new ComponentName(PACKAGE_NAME, cmpActivityName), null, activityIntent, 0);
+                new ComponentName(PACKAGE_NAME, cmpActivityName), null, activityIntent,
+                PackageManager.ResolveInfoFlags.of(0));
         checkActivityInfoName(ACTIVITY_NAME, listWithDiff);
 
         // List with the same activities to make filter work
         List<ResolveInfo> listInSame = mPackageManager.queryIntentActivityOptions(
-                new ComponentName(PACKAGE_NAME, ACTIVITY_NAME), null, activityIntent, 0);
+                new ComponentName(PACKAGE_NAME, ACTIVITY_NAME), null, activityIntent,
+                PackageManager.ResolveInfoFlags.of(0));
         assertEquals(0, listInSame.size());
 
         // Test queryIntentActivities
         List<ResolveInfo> intentActivities =
-                mPackageManager.queryIntentActivities(activityIntent, 0);
+                mPackageManager.queryIntentActivities(activityIntent,
+                        PackageManager.ResolveInfoFlags.of(0));
         assertTrue(intentActivities.size() > 0);
         checkActivityInfoName(ACTIVITY_NAME, intentActivities);
 
@@ -213,14 +282,14 @@
         // Test queryIntentServices
         Intent serviceIntent = new Intent(SERVICE_ACTION_NAME);
         List<ResolveInfo> services = mPackageManager.queryIntentServices(serviceIntent,
-                0 /*flags*/);
+                PackageManager.ResolveInfoFlags.of(0));
         checkServiceInfoName(SERVICE_NAME, services);
 
         // Test queryBroadcastReceivers
-        String receiverActionName = "android.content.pm.cts.PackageManagerTest.PMTEST_RECEIVER";
-        Intent broadcastIntent = new Intent(receiverActionName);
-        List<ResolveInfo> broadcastReceivers = new ArrayList<ResolveInfo>();
-        broadcastReceivers = mPackageManager.queryBroadcastReceivers(broadcastIntent, 0);
+        Intent broadcastIntent = new Intent(RECEIVER_ACTION_NAME);
+        List<ResolveInfo> broadcastReceivers =
+                mPackageManager.queryBroadcastReceivers(broadcastIntent,
+                        PackageManager.ResolveInfoFlags.of(0));
         checkActivityInfoName(RECEIVER_NAME, broadcastReceivers);
 
         // Test queryPermissionsByGroup, queryContentProviders
@@ -229,12 +298,208 @@
                 testPermissionsGroup, PackageManager.GET_META_DATA);
         checkPermissionInfoName(CALL_ABROAD_PERMISSION_NAME, permissions);
 
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         List<ProviderInfo> providers = mPackageManager.queryContentProviders(PACKAGE_NAME,
-                appInfo.uid, 0);
+                appInfo.uid, PackageManager.ComponentInfoFlags.of(0));
         checkProviderInfoName(PROVIDER_NAME, providers);
     }
 
+    @Test
+    public void testEnforceIntentToMatchIntentFilter() {
+        Intent intent = new Intent();
+        List<ResolveInfo> results;
+
+        /* Implicit intent tests */
+
+        intent.setPackage(INTENT_RESOLUTION_TEST_PKG_NAME);
+
+        // Implicit intents with matching intent filter
+        intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+
+        // Implicit intents with non-matching intent filter
+        intent.setAction(NON_EXISTENT_ACTION_NAME);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+
+        /* Explicit intent tests */
+
+        intent = new Intent();
+        ComponentName comp;
+
+        // Explicit intents with matching intent filter
+        intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, SERVICE_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, RECEIVER_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+
+        // Explicit intents with non-matching intent filter on target T+
+        intent.setAction(NON_EXISTENT_ACTION_NAME);
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, SERVICE_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, RECEIVER_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+
+        // More comprehensive intent matching tests on target T+
+        intent = new Intent();
+        comp = new ComponentName(INTENT_RESOLUTION_TEST_PKG_NAME, ACTIVITY_NAME);
+        intent.setComponent(comp);
+        intent.setAction(RESOLUTION_TEST_ACTION_NAME + "2");
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        intent.setType("*/*");
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        intent.setData(Uri.parse("http://example.com"));
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        intent.setDataAndType(Uri.parse("http://example.com"), "*/*");
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        File file = new File(mContext.getFilesDir(), "test.txt");
+        try {
+            file.createNewFile();
+        } catch (IOException e) {
+            fail(e.getMessage());
+        }
+        Uri uri = FileProvider.getUriForFile(mContext, FILE_PROVIDER_AUTHORITY, file);
+        intent.setData(uri);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        file.delete();
+        intent.addCategory(Intent.CATEGORY_APP_BROWSER);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+
+        // Explicit intents with non-matching intent filter on target < T
+        final String api30Pkg = INTENT_RESOLUTION_TEST_PKG_NAME + "Api30";
+        intent.setAction(NON_EXISTENT_ACTION_NAME);
+        comp = new ComponentName(api30Pkg, ACTIVITY_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(api30Pkg, SERVICE_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(api30Pkg, RECEIVER_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+
+        // Explicit intents with non-matching intent filter on our own package
+        intent.setAction(NON_EXISTENT_ACTION_NAME);
+        comp = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(PACKAGE_NAME, SERVICE_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        comp = new ComponentName(PACKAGE_NAME, RECEIVER_NAME);
+        intent.setComponent(comp);
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+
+        /* Intent selector tests */
+
+        Intent selector = new Intent();
+        selector.setPackage(INTENT_RESOLUTION_TEST_PKG_NAME);
+        intent = new Intent();
+        intent.setSelector(selector);
+
+        // Matching intent and matching selector
+        selector.setAction(SELECTOR_ACTION_NAME);
+        intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(1, results.size());
+
+        // Matching intent and non-matching selector
+        selector.setAction(NON_EXISTENT_ACTION_NAME);
+        intent.setAction(RESOLUTION_TEST_ACTION_NAME);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+
+        // Non-matching intent and matching selector
+        selector.setAction(SELECTOR_ACTION_NAME);
+        intent.setAction(NON_EXISTENT_ACTION_NAME);
+        results = mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryIntentServices(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+        results = mPackageManager.queryBroadcastReceivers(intent,
+                PackageManager.ResolveInfoFlags.of(0));
+        assertEquals(0, results.size());
+    }
+
     private void checkActivityInfoName(String expectedName, List<ResolveInfo> resolves) {
         // Flag for checking if the name is contained in list array.
         boolean isContained = false;
@@ -305,7 +570,8 @@
     @Test
     public void testGetInfo() throws NameNotFoundException {
         // Test getApplicationInfo, getText
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         int discriptionRes = R.string.hello_android;
         String expectedDisciptionRes = "Hello, Android!";
         CharSequence appText = mPackageManager.getText(PACKAGE_NAME, discriptionRes, appInfo);
@@ -317,7 +583,7 @@
 
         // Test getPackageInfo
         PackageInfo packageInfo = mPackageManager.getPackageInfo(PACKAGE_NAME,
-                PackageManager.GET_INSTRUMENTATION);
+                PackageManager.PackageInfoFlags.of(PackageManager.GET_INSTRUMENTATION));
         assertEquals(PACKAGE_NAME, packageInfo.packageName);
 
         // Test getApplicationInfo, getApplicationLabel
@@ -327,22 +593,25 @@
 
         // Test getServiceInfo
         assertEquals(SERVICE_NAME, mPackageManager.getServiceInfo(serviceName,
-                PackageManager.GET_META_DATA).name);
+                PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA)).name);
 
         // Test getReceiverInfo
-        assertEquals(RECEIVER_NAME, mPackageManager.getReceiverInfo(receiverName, 0).name);
+        assertEquals(RECEIVER_NAME, mPackageManager.getReceiverInfo(receiverName,
+                PackageManager.ComponentInfoFlags.of(0)).name);
 
         // Test getPackageArchiveInfo
         final String apkRoute = mContext.getPackageCodePath();
         final String apkName = mContext.getPackageName();
-        assertEquals(apkName, mPackageManager.getPackageArchiveInfo(apkRoute, 0).packageName);
+        assertEquals(apkName, mPackageManager.getPackageArchiveInfo(apkRoute,
+                PackageManager.PackageInfoFlags.of(0)).packageName);
 
         // Test getPackagesForUid, getNameForUid
         checkPackagesNameForUid(PACKAGE_NAME, mPackageManager.getPackagesForUid(appInfo.uid));
         assertEquals(PACKAGE_NAME, mPackageManager.getNameForUid(appInfo.uid));
 
         // Test getActivityInfo
-        assertEquals(ACTIVITY_NAME, mPackageManager.getActivityInfo(activityName, 0).name);
+        assertEquals(ACTIVITY_NAME, mPackageManager.getActivityInfo(activityName,
+                PackageManager.ComponentInfoFlags.of(0)).name);
 
         // Test getPackageGids
         assertTrue(mPackageManager.getPackageGids(PACKAGE_NAME).length > 0);
@@ -360,10 +629,12 @@
         checkPermissionGroupInfoName(PERMISSIONGROUP_NAME, permissionGroups);
 
         // Test getInstalledApplications
-        assertTrue(mPackageManager.getInstalledApplications(PackageManager.GET_META_DATA).size() > 0);
+        assertTrue(mPackageManager.getInstalledApplications(
+                PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA)).size() > 0);
 
         // Test getInstalledPacakge
-        assertTrue(mPackageManager.getInstalledPackages(0).size() > 0);
+        assertTrue(mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(0)).size() > 0);
 
         // Test getInstrumentationInfo
         assertEquals(INSTRUMENT_NAME, mPackageManager.getInstrumentationInfo(instrName, 0).name);
@@ -383,8 +654,8 @@
         assertFalse(mPackageManager.isSafeMode());
 
         // Test getTargetSdkVersion
-        int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0)
-                .targetSdkVersion;
+        int expectedTargetSdk = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0)).targetSdkVersion;
         assertEquals(expectedTargetSdk, mPackageManager.getTargetSdkVersion(PACKAGE_NAME));
         assertThrows(PackageManager.NameNotFoundException.class,
                 () -> mPackageManager.getTargetSdkVersion(
@@ -422,6 +693,7 @@
      * Simple test for {@link PackageManager#getPreferredActivities(List, List, String)} that tests
      * calling it has no effect. The method is essentially a no-op because no preferred activities
      * can be added.
+     *
      * @see PackageManager#addPreferredActivity(IntentFilter, int, ComponentName[], ComponentName)
      */
     @Test
@@ -508,14 +780,14 @@
     @Test
     public void testAccessEnabledSetting() {
         mPackageManager.setApplicationEnabledSetting(PACKAGE_NAME,
-                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
-        assertEquals(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+        assertEquals(COMPONENT_ENABLED_STATE_ENABLED,
                 mPackageManager.getApplicationEnabledSetting(PACKAGE_NAME));
 
         ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
         mPackageManager.setComponentEnabledSetting(componentName,
-                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
-        assertEquals(PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
+                COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
+        assertEquals(COMPONENT_ENABLED_STATE_ENABLED,
                 mPackageManager.getComponentEnabledSetting(componentName));
     }
 
@@ -549,7 +821,8 @@
         // getDrawable is called by ComponentInfo.loadIcon() which called by getActivityIcon()
         // method of PackageMaganer. Here is just assurance for its functionality.
         int iconRes = R.drawable.start;
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         assertNotNull(mPackageManager.getDrawable(PACKAGE_NAME, iconRes, appInfo));
     }
 
@@ -568,8 +841,10 @@
     public void testCheckSignaturesMatch_byUid() throws NameNotFoundException {
         // Compare the signature of this package to another package installed by this test suite
         // (see AndroidTest.xml). Their signatures must match.
-        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
-        int uid2 = mPackageManager.getPackageInfo("com.android.cts.stub", 0).applicationInfo.uid;
+        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
+        int uid2 = mPackageManager.getPackageInfo("com.android.cts.stub",
+                PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
         assertEquals(PackageManager.SIGNATURE_MATCH, mPackageManager.checkSignatures(uid1, uid2));
 
         // A UID's signature should match its own signature.
@@ -586,8 +861,10 @@
     @Test
     public void testCheckSignaturesNoMatch_byUid() throws NameNotFoundException {
         // This test package's signature shouldn't match the system's signature.
-        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME, 0).applicationInfo.uid;
-        int uid2 = mPackageManager.getPackageInfo("android", 0).applicationInfo.uid;
+        int uid1 = mPackageManager.getPackageInfo(PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
+        int uid2 = mPackageManager.getPackageInfo("android",
+                PackageManager.PackageInfoFlags.of(0)).applicationInfo.uid;
         assertEquals(PackageManager.SIGNATURE_NO_MATCH,
                 mPackageManager.checkSignatures(uid1, uid2));
     }
@@ -616,18 +893,21 @@
         Intent intent = new Intent(ACTIVITY_ACTION_NAME);
         intent.setComponent(new ComponentName(PACKAGE_NAME, ACTIVITY_NAME));
         assertEquals(ACTIVITY_NAME, mPackageManager.resolveActivity(intent,
-                PackageManager.MATCH_DEFAULT_ONLY).activityInfo.name);
+                PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY))
+                .activityInfo.name);
 
         // Test resolveService
         intent = new Intent(SERVICE_ACTION_NAME);
         intent.setComponent(new ComponentName(PACKAGE_NAME, SERVICE_NAME));
-        ResolveInfo resolveInfo = mPackageManager.resolveService(intent, 0 /*flags*/);
+        ResolveInfo resolveInfo = mPackageManager.resolveService(intent,
+                PackageManager.ResolveInfoFlags.of(0));
         assertEquals(SERVICE_NAME, resolveInfo.serviceInfo.name);
 
         // Test resolveContentProvider
         String providerAuthorities = "ctstest";
         assertEquals(PROVIDER_NAME,
-                mPackageManager.resolveContentProvider(providerAuthorities, 0).name);
+                mPackageManager.resolveContentProvider(providerAuthorities,
+                        PackageManager.ComponentInfoFlags.of(0)).name);
     }
 
     @Test
@@ -635,7 +915,8 @@
         ComponentName componentName = new ComponentName(PACKAGE_NAME, ACTIVITY_NAME);
         int resourceId = R.xml.pm_test;
         String xmlName = "android.content.cts:xml/pm_test";
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         assertNotNull(mPackageManager.getXml(PACKAGE_NAME, resourceId, appInfo));
         assertEquals(xmlName, mPackageManager.getResourcesForActivity(componentName)
                 .getResourceName(resourceId));
@@ -648,7 +929,8 @@
     @Test
     public void testGetResources_withConfig() throws NameNotFoundException {
         int resourceId = R.string.config_overridden_string;
-        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME, 0);
+        ApplicationInfo appInfo = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
 
         Configuration c1 = new Configuration(mContext.getResources().getConfiguration());
         c1.orientation = Configuration.ORIENTATION_PORTRAIT;
@@ -668,7 +950,8 @@
 
         final int flags = PackageManager.GET_SIGNATURES;
 
-        final PackageInfo pkgInfo = mPackageManager.getPackageArchiveInfo(apkPath, flags);
+        final PackageInfo pkgInfo = mPackageManager.getPackageArchiveInfo(apkPath,
+                PackageManager.PackageInfoFlags.of(flags));
 
         assertEquals("getPackageArchiveInfo should return the correct package name",
                 apkName, pkgInfo.packageName);
@@ -690,8 +973,9 @@
     @Test
     public void testGetNamesForUids_valid() throws Exception {
         final int shimId =
-                mPackageManager.getApplicationInfo("com.android.cts.ctsshim", 0 /*flags*/).uid;
-        final int[] uids = new int[] {
+                mPackageManager.getApplicationInfo("com.android.cts.ctsshim",
+                        PackageManager.ApplicationInfoFlags.of(0)).uid;
+        final int[] uids = new int[]{
                 1000,
                 Integer.MAX_VALUE,
                 shimId,
@@ -710,10 +994,13 @@
         int userId = mContext.getUserId();
         int expectedUid = UserHandle.getUid(userId, 1000);
 
-        assertEquals(expectedUid, mPackageManager.getPackageUid("android", 0));
+        assertEquals(expectedUid, mPackageManager.getPackageUid("android",
+                PackageManager.PackageInfoFlags.of(0)));
 
-        int uid = mPackageManager.getApplicationInfo("com.android.cts.ctsshim", 0 /*flags*/).uid;
-        assertEquals(uid, mPackageManager.getPackageUid("com.android.cts.ctsshim", 0));
+        int uid = mPackageManager.getApplicationInfo("com.android.cts.ctsshim",
+                PackageManager.ApplicationInfoFlags.of(0)).uid;
+        assertEquals(uid, mPackageManager.getPackageUid("com.android.cts.ctsshim",
+                PackageManager.PackageInfoFlags.of(0)));
     }
 
     @Test
@@ -726,7 +1013,8 @@
     @Test
     public void testGetPackageInfo_notFound() {
         try {
-            mPackageManager.getPackageInfo("this.package.does.not.exist", 0);
+            mPackageManager.getPackageInfo("this.package.does.not.exist",
+                    PackageManager.PackageInfoFlags.of(0));
             fail("Exception expected");
         } catch (NameNotFoundException expected) {
         }
@@ -734,8 +1022,10 @@
 
     @Test
     public void testGetInstalledPackages() throws Exception {
-        List<PackageInfo> pkgs = mPackageManager.getInstalledPackages(GET_META_DATA
-                | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS | GET_SERVICES | GET_RECEIVERS);
+        List<PackageInfo> pkgs = mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(
+                        GET_META_DATA | GET_PERMISSIONS | GET_ACTIVITIES | GET_PROVIDERS
+                                | GET_SERVICES | GET_RECEIVERS));
 
         PackageInfo pkgInfo = findPackageOrFail(pkgs, PACKAGE_NAME);
         assertTestPackageInfo(pkgInfo);
@@ -819,7 +1109,8 @@
     // Tests that other packages can be queried.
     @Test
     public void testGetInstalledPackages_OtherPackages() throws Exception {
-        List<PackageInfo> pkgInfos = mPackageManager.getInstalledPackages(0);
+        List<PackageInfo> pkgInfos = mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(0));
 
         // Check a normal package.
         PackageInfo pkgInfo = findPackageOrFail(pkgInfos, "com.android.cts.stub"); // A test package
@@ -832,10 +1123,11 @@
 
     @Test
     public void testGetInstalledApplications() throws Exception {
-        List<ApplicationInfo> apps = mPackageManager.getInstalledApplications(GET_META_DATA);
+        List<ApplicationInfo> apps = mPackageManager.getInstalledApplications(
+                PackageManager.ApplicationInfoFlags.of(GET_META_DATA));
 
         ApplicationInfo app = findPackageItemOrFail(
-                apps.toArray(new ApplicationInfo[] {}), APPLICATION_NAME);
+                apps.toArray(new ApplicationInfo[]{}), APPLICATION_NAME);
 
         assertEquals(APPLICATION_NAME, app.name);
         assertEquals("Android TestCase", app.loadLabel(mPackageManager));
@@ -868,11 +1160,12 @@
     @Test
     public void testGetPackagesHoldingPermissions() {
         List<PackageInfo> pkgInfos = mPackageManager.getPackagesHoldingPermissions(
-                new String[] { GRANTED_PERMISSION_NAME }, 0);
+                new String[]{GRANTED_PERMISSION_NAME}, PackageManager.PackageInfoFlags.of(0));
         findPackageOrFail(pkgInfos, PACKAGE_NAME);
 
         pkgInfos = mPackageManager.getPackagesHoldingPermissions(
-                new String[] { NOT_GRANTED_PERMISSION_NAME }, 0);
+                new String[]{NOT_GRANTED_PERMISSION_NAME},
+                PackageManager.PackageInfoFlags.of(0));
         for (PackageInfo pkgInfo : pkgInfos) {
             if (PACKAGE_NAME.equals(pkgInfo.packageName)) {
                 fail("Must not return package " + PACKAGE_NAME);
@@ -999,7 +1292,8 @@
             boolean isAppStillVisible = true;
             while (SystemClock.elapsedRealtime() < startTimeMs + timeoutMs) {
                 try {
-                    mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+                    mPackageManager.getPackageInfo(packageToManipulate,
+                            PackageManager.PackageInfoFlags.of(MATCH_SYSTEM_ONLY));
                 } catch (NameNotFoundException e) {
                     // expected, stop polling
                     isAppStillVisible = false;
@@ -1018,7 +1312,8 @@
                     mPackageManager.setSystemAppState(packageToManipulate,
                             PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_VISIBLE));
             try {
-                mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+                mPackageManager.getPackageInfo(packageToManipulate,
+                        PackageManager.PackageInfoFlags.of(MATCH_SYSTEM_ONLY));
             } catch (NameNotFoundException e) {
                 fail(packageToManipulate
                         + " should be found via getPackageInfo after re-enabling.");
@@ -1031,7 +1326,8 @@
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
         PackageInfo packageInfo = mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
-                PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
+                PackageManager.PackageInfoFlags.of(
+                        PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY));
         assertShimApexInfoIsCorrect(packageInfo);
     }
 
@@ -1040,7 +1336,8 @@
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
         try {
-            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0 /* flags */);
+            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+                    PackageManager.PackageInfoFlags.of(0));
             fail("NameNotFoundException expected");
         } catch (NameNotFoundException expected) {
         }
@@ -1051,7 +1348,8 @@
         assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
 
         try {
-            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
             fail("NameNotFoundException expected");
         } catch (NameNotFoundException expected) {
         }
@@ -1062,7 +1360,8 @@
         assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
 
         try {
-            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME, 0);
+            mPackageManager.getPackageInfo(SHIM_APEX_PACKAGE_NAME,
+                    PackageManager.PackageInfoFlags.of(0));
             fail("NameNotFoundException expected");
         } catch (NameNotFoundException expected) {
         }
@@ -1073,7 +1372,8 @@
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
-                PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY);
+                PackageManager.PackageInfoFlags.of(
+                        PackageManager.MATCH_APEX | PackageManager.MATCH_FACTORY_ONLY));
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
                 Collectors.toList());
@@ -1085,7 +1385,8 @@
     public void testGetInstalledPackages_ApexSupported_DoesNotMatchApex() {
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
-        List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
+        List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(0));
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
                 Collectors.toList());
@@ -1097,7 +1398,7 @@
         assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
 
         List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
-                PackageManager.MATCH_APEX);
+                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
                 Collectors.toList());
@@ -1108,7 +1409,8 @@
     public void testGetInstalledPackages_ApexNotSupported_DoesNotMatchApex() {
         assumeFalse("Device supports updating APEX", isUpdatingApexSupported());
 
-        List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(0);
+        List<PackageInfo> installedPackages = mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(0));
         List<PackageInfo> shimApex = installedPackages.stream().filter(
                 packageInfo -> packageInfo.packageName.equals(SHIM_APEX_PACKAGE_NAME)).collect(
                 Collectors.toList());
@@ -1122,7 +1424,8 @@
     @Test
     public void testGetInfo_noMetaData_InPackage() throws Exception {
         final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
-                GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+                PackageManager.PackageInfoFlags.of(
+                        GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS));
 
         assertThat(info.applicationInfo.metaData).isNull();
         Arrays.stream(info.activities).forEach(i -> assertThat(i.metaData).isNull());
@@ -1137,7 +1440,8 @@
      */
     @Test
     public void testGetInfo_noMetaData_InApplication() throws Exception {
-        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, /* flags */ 0);
+        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
         assertThat(ai.metaData).isNull();
     }
 
@@ -1148,7 +1452,8 @@
     @Test
     public void testGetInfo_noMetaData_InActivity() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
-        final ActivityInfo info = mPackageManager.getActivityInfo(componentName, /* flags */ 0);
+        final ActivityInfo info = mPackageManager.getActivityInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(0));
         assertThat(info.metaData).isNull();
     }
 
@@ -1159,7 +1464,8 @@
     @Test
     public void testGetInfo_noMetaData_InService() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockService.class);
-        final ServiceInfo info = mPackageManager.getServiceInfo(componentName, /* flags */ 0);
+        final ServiceInfo info = mPackageManager.getServiceInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(0));
         assertThat(info.metaData).isNull();
     }
 
@@ -1170,7 +1476,8 @@
     @Test
     public void testGetInfo_noMetaData_InBroadcastReceiver() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
-        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, /* flags */ 0);
+        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(0));
         assertThat(info.metaData).isNull();
     }
 
@@ -1181,7 +1488,8 @@
     @Test
     public void testGetInfo_noMetaData_InContentProvider() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
-        final ProviderInfo info = mPackageManager.getProviderInfo(componentName, /* flags */ 0);
+        final ProviderInfo info = mPackageManager.getProviderInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(0));
         assertThat(info.metaData).isNull();
     }
 
@@ -1192,7 +1500,9 @@
     @Test
     public void testGetInfo_checkMetaData_InPackage() throws Exception {
         final PackageInfo info = mPackageManager.getPackageInfo(PACKAGE_NAME,
-                GET_META_DATA | GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS | GET_PROVIDERS);
+                PackageManager.PackageInfoFlags.of(
+                        GET_META_DATA | GET_ACTIVITIES | GET_SERVICES | GET_RECEIVERS
+                                | GET_PROVIDERS));
 
         checkMetaData(new PackageItemInfo(info.applicationInfo));
         checkMetaData(new PackageItemInfo(
@@ -1211,7 +1521,8 @@
      */
     @Test
     public void testGetInfo_checkMetaData_InApplication() throws Exception {
-        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME, GET_META_DATA);
+        final ApplicationInfo ai = mPackageManager.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(GET_META_DATA));
         checkMetaData(new PackageItemInfo(ai));
     }
 
@@ -1222,7 +1533,8 @@
     @Test
     public void testGetInfo_checkMetaData_InActivity() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockActivity.class);
-        final ActivityInfo ai = mPackageManager.getActivityInfo(componentName, GET_META_DATA);
+        final ActivityInfo ai = mPackageManager.getActivityInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(GET_META_DATA));
         checkMetaData(new PackageItemInfo(ai));
     }
 
@@ -1233,7 +1545,8 @@
     @Test
     public void testGetInfo_checkMetaData_InService() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockService.class);
-        final ServiceInfo info = mPackageManager.getServiceInfo(componentName, GET_META_DATA);
+        final ServiceInfo info = mPackageManager.getServiceInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(GET_META_DATA));
         checkMetaData(new PackageItemInfo(info));
     }
 
@@ -1244,7 +1557,8 @@
     @Test
     public void testGetInfo_checkMetaData_InBroadcastReceiver() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockReceiver.class);
-        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName, GET_META_DATA);
+        final ActivityInfo info = mPackageManager.getReceiverInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(GET_META_DATA));
         checkMetaData(new PackageItemInfo(info));
     }
 
@@ -1255,7 +1569,8 @@
     @Test
     public void testGetInfo_checkMetaData_InContentProvider() throws Exception {
         final ComponentName componentName = new ComponentName(mContext, MockContentProvider.class);
-        final ProviderInfo info = mPackageManager.getProviderInfo(componentName, GET_META_DATA);
+        final ProviderInfo info = mPackageManager.getProviderInfo(componentName,
+                PackageManager.ComponentInfoFlags.of(GET_META_DATA));
         checkMetaData(new PackageItemInfo(info));
     }
 
@@ -1290,7 +1605,7 @@
             assertThat(xml.getAttributeIntValue(null, "rawColor", 0)).isEqualTo(0xffffff00);
             assertThat(xml.getAttributeValue(null, "rawColor")).isEqualTo("#ffffff00");
 
-            a = res.obtainAttributes(xml, new int[] {android.R.attr.text, android.R.attr.color});
+            a = res.obtainAttributes(xml, new int[]{android.R.attr.text, android.R.attr.color});
             assertThat(a.getString(0)).isEqualTo("metadata text");
             assertThat(a.getColor(1, 0)).isEqualTo(0xffff0000);
             assertThat(a.getString(1)).isEqualTo("#ffff0000");
@@ -1309,7 +1624,8 @@
         assumeTrue("Device doesn't support updating APEX", isUpdatingApexSupported());
 
         ApplicationInfo ai = mPackageManager.getApplicationInfo(
-                SHIM_APEX_PACKAGE_NAME, PackageManager.MATCH_APEX);
+                SHIM_APEX_PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_APEX));
         assertThat(ai.sourceDir).isEqualTo("/system/apex/com.android.apex.cts.shim.apex");
         assertThat(ai.publicSourceDir).isEqualTo(ai.sourceDir);
         assertThat(ai.flags & ApplicationInfo.FLAG_SYSTEM).isEqualTo(ApplicationInfo.FLAG_SYSTEM);
@@ -1323,7 +1639,7 @@
         final boolean useRoundIcon = mContext.getResources().getBoolean(
                 mContext.getResources().getIdentifier("config_useRoundIcon", "bool", "android"));
         final ApplicationInfo info = mPackageManager.getApplicationInfo(HELLO_WORLD_PACKAGE_NAME,
-                0 /*flags*/);
+                PackageManager.ApplicationInfoFlags.of(0));
         assertThat(info.icon).isEqualTo((useRoundIcon ? info.roundIconRes : info.iconRes));
     }
 
@@ -1348,8 +1664,9 @@
 
     /**
      * Runs a test for all combinations of a set of flags
+     *
      * @param flagValues Which flags to use
-     * @param test The test
+     * @param test       The test
      */
     public void runTestWithFlags(int[] flagValues, Consumer<Integer> test) {
         for (int i = 0; i < (1 << flagValues.length); i++) {
@@ -1377,10 +1694,12 @@
         runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
                 this::testGetInstalledPackages_WithFactoryFlag_IsSubset);
     }
+
     public void testGetInstalledPackages_WithFactoryFlag_IsSubset(int flags) {
-        List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(flags);
+        List<PackageInfo> packageInfos = mPackageManager.getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(flags));
         List<PackageInfo> packageInfos2 = mPackageManager.getInstalledPackages(
-                flags | MATCH_FACTORY_ONLY);
+                PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
         Set<String> supersetNames =
                 packageInfos.stream().map(pi -> pi.packageName).collect(Collectors.toSet());
 
@@ -1401,9 +1720,11 @@
         runTestWithFlags(PACKAGE_INFO_MATCH_FLAGS,
                 this::testGetInstalledPackages_WithFactoryFlag_ImpliesSystem);
     }
+
     public void testGetInstalledPackages_WithFactoryFlag_ImpliesSystem(int flags) {
         List<PackageInfo> packageInfos =
-                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+                mPackageManager.getInstalledPackages(
+                        PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
         for (PackageInfo pi : packageInfos) {
             if (!pi.applicationInfo.isSystemApp()) {
                 throw new AssertionError(pi.packageName + " is not a system app.");
@@ -1421,25 +1742,10 @@
                 this::testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates);
     }
 
-    // TODO(b/200519752): Remove once the bug is fixed
-    private boolean containsUpdatedApex() {
-        List<PackageInfo> installedApexPackages =
-                mPackageManager.getInstalledPackages(PackageManager.MATCH_APEX);
-        return installedApexPackages.stream().anyMatch(
-                p -> p.applicationInfo.sourceDir.startsWith("/data/apex"));
-    }
-
     public void testGetInstalledPackages_WithFactoryFlag_ContainsNoDuplicates(int flags) {
-        // TODO(b/200519752): Due to the bug, if there are updated APEX modules, then test will fail
-        // for flag: 0x40002000 and its superset. Skip under that specific condition.
-        int flagToSkip = MATCH_UNINSTALLED_PACKAGES | MATCH_APEX;
-        if (containsUpdatedApex() && (flags & flagToSkip) == flagToSkip) {
-            // Return silently so that the test still gets run for other flag combination.
-            return;
-        }
-
         List<PackageInfo> packageInfos =
-                mPackageManager.getInstalledPackages(flags | MATCH_FACTORY_ONLY);
+                mPackageManager.getInstalledPackages(
+                        PackageManager.PackageInfoFlags.of(flags | MATCH_FACTORY_ONLY));
         Set<String> foundPackages = new HashSet<>();
         for (PackageInfo pi : packageInfos) {
             if (foundPackages.contains(pi.packageName)) {
@@ -1452,7 +1758,7 @@
     @Test
     public void testInstallTestOnlyPackagePermission_onlyGrantedToShell() {
         List<PackageInfo> packages = mPackageManager.getPackagesHoldingPermissions(
-                new String[]{INSTALL_TEST_ONLY_PACKAGE}, /* flags= */ 0);
+                new String[]{INSTALL_TEST_ONLY_PACKAGE}, PackageManager.PackageInfoFlags.of(0));
 
         assertThat(packages).hasSize(1);
         assertThat(packages.get(0).packageName).isEqualTo(SHELL_PACKAGE_NAME);
@@ -1479,7 +1785,12 @@
     }
 
     private boolean installPackage(String apkPath) {
-        return SystemUtil.runShellCommand("pm install -t " + apkPath).equals("Success\n");
+        return installPackage(apkPath, false /* dontKill */);
+    }
+
+    private boolean installPackage(String apkPath, boolean dontKill) {
+        return SystemUtil.runShellCommand(
+                "pm install -t " + (dontKill ? "--dont-kill " : "") + apkPath).equals("Success\n");
     }
 
     private void uninstallPackage(String packageName) {
@@ -1487,10 +1798,117 @@
     }
 
     @Test
+    public void testGetLaunchIntentSenderForPackage() throws Exception {
+        final Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                LauncherMockActivity.class.getName(), null /* result */, false /* block */);
+        mInstrumentation.addMonitor(monitor);
+
+        try {
+            final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+                    PACKAGE_NAME);
+            assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+            assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+            intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+                    null /* onFinished */, null /* handler */);
+            final Activity activity = monitor.waitForActivityWithTimeout(TIMEOUT_MS);
+            assertThat(activity).isNotNull();
+            activity.finish();
+        } finally {
+            mInstrumentation.removeMonitor(monitor);
+        }
+    }
+
+    @Test(expected = IntentSender.SendIntentException.class)
+    public void testGetLaunchIntentSenderForPackage_noMainActivity() throws Exception {
+        assertThat(installPackage(EMPTY_APP_APK)).isTrue();
+        final PackageInfo packageInfo = mPackageManager.getPackageInfo(EMPTY_APP_PACKAGE_NAME,
+                PackageManager.PackageInfoFlags.of(0));
+        assertThat(packageInfo.packageName).isEqualTo(EMPTY_APP_PACKAGE_NAME);
+        final Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setPackage(EMPTY_APP_PACKAGE_NAME);
+        assertThat(mPackageManager.queryIntentActivities(intent,
+                PackageManager.ResolveInfoFlags.of(0))).isEmpty();
+
+        final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+                EMPTY_APP_PACKAGE_NAME);
+        assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+        assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+        intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+                null /* onFinished */, null /* handler */);
+    }
+
+    @Test(expected = IntentSender.SendIntentException.class)
+    public void testGetLaunchIntentSenderForPackage_packageNotExist() throws Exception {
+        try {
+            mPackageManager.getPackageInfo(EMPTY_APP_PACKAGE_NAME,
+                    PackageManager.PackageInfoFlags.of(0));
+            fail(EMPTY_APP_PACKAGE_NAME + " should not exist in the device");
+        } catch (NameNotFoundException e) {
+        }
+        final IntentSender intentSender = mPackageManager.getLaunchIntentSenderForPackage(
+                EMPTY_APP_PACKAGE_NAME);
+        assertThat(intentSender.getCreatorPackage()).isEqualTo(PACKAGE_NAME);
+        assertThat(intentSender.getCreatorUid()).isEqualTo(mContext.getApplicationInfo().uid);
+
+        intentSender.sendIntent(mContext, 0 /* code */, null /* intent */,
+                null /* onFinished */, null /* handler */);
+    }
+
+    @Test
+    public void testDefaultHomeActivity_doesntChange_whenInstallAnotherLauncher() throws Exception {
+        final Intent homeIntent = new Intent(Intent.ACTION_MAIN)
+                .addCategory(Intent.CATEGORY_HOME);
+        final String currentHomeActivity =
+                mPackageManager.resolveActivity(homeIntent,
+                        PackageManager.ResolveInfoFlags.of(0)).activityInfo.name;
+
+        // Install another launcher app.
+        assertThat(installPackage(MOCK_LAUNCHER_APK)).isTrue();
+
+        // There is an async operation to re-set the default home activity in Role with no way
+        // to listen for completion once a package installed, so poll until the default home
+        // activity is set.
+        PollingCheck.waitFor(() -> currentHomeActivity.equals(
+                mPackageManager.resolveActivity(homeIntent,
+                        PackageManager.ResolveInfoFlags.of(0)).activityInfo.name));
+        final List<String> homeApps =
+                mPackageManager.queryIntentActivities(homeIntent,
+                                PackageManager.ResolveInfoFlags.of(0)).stream()
+                        .map(i -> i.activityInfo.packageName).collect(Collectors.toList());
+        assertThat(homeApps.contains(MOCK_LAUNCHER_PACKAGE_NAME)).isTrue();
+    }
+
+    @Test
+    public void setComponentEnabledSetting_nonExistentPackage_withoutPermission() {
+        final ComponentName componentName = ComponentName.createRelative(
+                NON_EXISTENT_PACKAGE_NAME, "ClassName");
+        assertThrows(SecurityException.class, () -> mPackageManager.setComponentEnabledSetting(
+                componentName, COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+    }
+
+    @Test
+    public void setComponentEnabledSetting_nonExistentPackage_hasPermission() {
+        final ComponentName componentName = ComponentName.createRelative(
+                NON_EXISTENT_PACKAGE_NAME, "ClassName");
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+
+        try {
+            assertThrows(IllegalArgumentException.class,
+                    () -> mPackageManager.setComponentEnabledSetting(componentName,
+                            COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+        } finally {
+            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void loadApplicationLabel_withLongLabelName_truncated() throws Exception {
         assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
         final ApplicationInfo info = mPackageManager.getApplicationInfo(
-                EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
+                EMPTY_APP_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
         final CharSequence resLabel = mPackageManager.getText(
                 EMPTY_APP_PACKAGE_NAME, info.labelRes, info);
 
@@ -1504,9 +1922,9 @@
         final ComponentName componentName = ComponentName.createRelative(
                 EMPTY_APP_PACKAGE_NAME, ".MockActivity");
         final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
-                EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
+                EMPTY_APP_PACKAGE_NAME, PackageManager.ApplicationInfoFlags.of(0));
         final ActivityInfo activityInfo = mPackageManager.getActivityInfo(
-                componentName, 0 /* flags */);
+                componentName, PackageManager.ComponentInfoFlags.of(0));
         final CharSequence resLabel = mPackageManager.getText(
                 EMPTY_APP_PACKAGE_NAME, activityInfo.labelRes, appInfo);
 
@@ -1514,4 +1932,270 @@
         assertThat(activityInfo.loadLabel(mPackageManager).length())
                 .isEqualTo(MAX_SAFE_LABEL_LENGTH);
     }
+
+    @Test
+    public void setComponentEnabledSettings_withDuplicatedComponent() {
+        final List<ComponentEnabledSetting> enabledSettings = List.of(
+                new ComponentEnabledSetting(
+                        ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+                new ComponentEnabledSetting(
+                        ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+    }
+
+    @Test
+    public void setComponentEnabledSettings_flagDontKillAppConflict() {
+        final List<ComponentEnabledSetting> enabledSettings = List.of(
+                new ComponentEnabledSetting(
+                        ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+                new ComponentEnabledSetting(
+                        SERVICE_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+    }
+
+    @Test
+    public void setComponentEnabledSettings_disableSelfAndStubApp_withoutPermission() {
+        final List<ComponentEnabledSetting> enabledSettings = List.of(
+                new ComponentEnabledSetting(
+                        ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+                new ComponentEnabledSetting(
+                        STUB_ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+
+        assertThrows(SecurityException.class,
+                () -> mPackageManager.setComponentEnabledSettings(enabledSettings));
+    }
+
+    @Test
+    public void setComponentEnabledSettings_disableSelf() throws Exception {
+        final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+        final int serviceState = mPackageManager.getComponentEnabledSetting(SERVICE_COMPONENT);
+        assertThat(activityState).isAnyOf(
+                COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+        assertThat(serviceState).isAnyOf(
+                COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+
+        try {
+            final List<ComponentEnabledSetting> enabledSettings = List.of(
+                    new ComponentEnabledSetting(
+                            ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+                    new ComponentEnabledSetting(
+                            SERVICE_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP));
+            setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+        } finally {
+            final List<ComponentEnabledSetting> enabledSettings = List.of(
+                    new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+                    new ComponentEnabledSetting(SERVICE_COMPONENT, serviceState, DONT_KILL_APP));
+            setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+        }
+    }
+
+    @Test
+    public void setComponentEnabledSettings_disableSelfAndStubApp_killStubApp()
+            throws Exception {
+        final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+        final int stubState = mPackageManager.getComponentEnabledSetting(STUB_ACTIVITY_COMPONENT);
+        assertThat(activityState).isAnyOf(
+                COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+        assertThat(stubState).isAnyOf(
+                COMPONENT_ENABLED_STATE_DEFAULT, COMPONENT_ENABLED_STATE_ENABLED);
+
+        final Intent intent = new Intent();
+        intent.setComponent(STUB_SERVICE_COMPONENT);
+        final AtomicBoolean killed = new AtomicBoolean();
+        mServiceTestRule.bindService(intent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                killed.set(true);
+            }
+        }, Context.BIND_AUTO_CREATE);
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+
+        try {
+            final List<ComponentEnabledSetting> enabledSettings = List.of(
+                    new ComponentEnabledSetting(
+                            ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP),
+                    new ComponentEnabledSetting(
+                            STUB_ACTIVITY_COMPONENT, COMPONENT_ENABLED_STATE_DISABLED, 0));
+            setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+            TestUtils.waitUntil("Waiting for the process " + STUB_PACKAGE_NAME
+                    + " to die", () -> killed.get());
+        } finally {
+            final List<ComponentEnabledSetting> enabledSettings = List.of(
+                    new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+                    new ComponentEnabledSetting(STUB_ACTIVITY_COMPONENT, stubState, 0));
+            setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings);
+            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void setComponentEnabledSettings_noStateChanged_noBroadcastReceived() {
+        final int activityState = mPackageManager.getComponentEnabledSetting(ACTIVITY_COMPONENT);
+        final int serviceState = mPackageManager.getComponentEnabledSetting(SERVICE_COMPONENT);
+        final List<ComponentEnabledSetting> enabledSettings = List.of(
+                new ComponentEnabledSetting(ACTIVITY_COMPONENT, activityState, DONT_KILL_APP),
+                new ComponentEnabledSetting(SERVICE_COMPONENT, serviceState, DONT_KILL_APP));
+
+        assertThrows(TimeoutException.class,
+                () -> setComponentEnabledSettingsAndWaitForBroadcasts(enabledSettings));
+    }
+
+    @Test
+    public void clearApplicationUserData_resetComponentEnabledSettings() throws Exception {
+        assertThat(installPackage(MOCK_LAUNCHER_APK)).isTrue();
+        final List<ComponentEnabledSetting> settings = List.of(
+                new ComponentEnabledSetting(RESET_ENABLED_SETTING_ACTIVITY_COMPONENT,
+                        COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+                new ComponentEnabledSetting(RESET_ENABLED_SETTING_RECEIVER_COMPONENT,
+                        COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+                new ComponentEnabledSetting(RESET_ENABLED_SETTING_SERVICE_COMPONENT,
+                        COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */),
+                new ComponentEnabledSetting(RESET_ENABLED_SETTING_PROVIDER_COMPONENT,
+                        COMPONENT_ENABLED_STATE_ENABLED, 0 /* flags */));
+
+        try {
+            mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
+                    android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
+            // update component enabled settings
+            setComponentEnabledSettingsAndWaitForBroadcasts(settings);
+
+            clearApplicationUserData(MOCK_LAUNCHER_PACKAGE_NAME);
+
+            assertThat(mPackageManager
+                    .getComponentEnabledSetting(RESET_ENABLED_SETTING_ACTIVITY_COMPONENT))
+                    .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+            assertThat(mPackageManager
+                    .getComponentEnabledSetting(RESET_ENABLED_SETTING_RECEIVER_COMPONENT))
+                    .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+            assertThat(mPackageManager
+                    .getComponentEnabledSetting(RESET_ENABLED_SETTING_SERVICE_COMPONENT))
+                    .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+            assertThat(mPackageManager
+                    .getComponentEnabledSetting(RESET_ENABLED_SETTING_PROVIDER_COMPONENT))
+                    .isEqualTo(COMPONENT_ENABLED_STATE_DEFAULT);
+        } finally {
+            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    private void setComponentEnabledSettingsAndWaitForBroadcasts(
+            List<ComponentEnabledSetting> enabledSettings)
+            throws InterruptedException, TimeoutException {
+        final List<ComponentName> componentsToWait = enabledSettings.stream()
+                .map(enabledSetting -> enabledSetting.getComponentName())
+                .collect(Collectors.toList());
+        final IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        filter.addDataScheme("package");
+        final CountDownLatch latch = new CountDownLatch(1 /* count */);
+        final BroadcastReceiver br = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                final String packageName = intent.getData() != null
+                        ? intent.getData().getSchemeSpecificPart() : null;
+                final String[] receivedComponents = intent.getStringArrayExtra(
+                        Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+                if (packageName == null || receivedComponents == null) {
+                    return;
+                }
+                for (String componentString : receivedComponents) {
+                    componentsToWait.remove(new ComponentName(packageName, componentString));
+                }
+                if (componentsToWait.isEmpty()) {
+                    latch.countDown();
+                }
+            }
+        };
+        mContext.registerReceiver(br, filter);
+        try {
+            mPackageManager.setComponentEnabledSettings(enabledSettings);
+            if (!latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+                throw new TimeoutException("Package changed broadcasts for " + componentsToWait
+                        + " not received in " + TIMEOUT_MS + "ms");
+            }
+            for (ComponentEnabledSetting enabledSetting : enabledSettings) {
+                assertThat(mPackageManager.getComponentEnabledSetting(
+                        enabledSetting.getComponentName()))
+                        .isEqualTo(enabledSetting.getEnabledState());
+            }
+        } finally {
+            mContext.unregisterReceiver(br);
+        }
+    }
+
+    private void clearApplicationUserData(String packageName) {
+        final StringBuilder cmd = new StringBuilder("pm clear --user ");
+        cmd.append(UserHandle.myUserId()).append(" ");
+        cmd.append(packageName);
+        SystemUtil.runShellCommand(cmd.toString());
+    }
+
+    @Test
+    public void testPrebuiltSharedLibraries_existOnDevice() {
+        final List<SharedLibraryInfo> infos =
+                mPackageManager.getSharedLibraries(PackageManager.PackageInfoFlags.of(0)).stream()
+                        .filter(info -> info.isBuiltin() && !info.isNative())
+                        .collect(Collectors.toList());
+        assertThat(infos).isNotEmpty();
+
+        final List<SharedLibraryInfo> fileNotExistInfos = infos.stream()
+                .filter(info -> !(new File(info.getPath()).exists())).collect(
+                        Collectors.toList());
+        assertThat(fileNotExistInfos).isEmpty();
+    }
+
+    @Test
+    public void testInstallUpdate_applicationIsKilled() throws Exception {
+        final Intent intent = new Intent();
+        intent.setComponent(STUB_SERVICE_COMPONENT);
+        final AtomicBoolean killed = new AtomicBoolean();
+        mServiceTestRule.bindService(intent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                killed.set(true);
+            }
+        }, Context.BIND_AUTO_CREATE);
+
+        installPackage(STUB_PACKAGE_APK);
+        // The application should be killed after updating.
+        TestUtils.waitUntil("Waiting for the process " + STUB_PACKAGE_NAME + " to die",
+                10 /* timeoutSecond */, () -> killed.get());
+    }
+
+    @Test
+    public void testInstallUpdate_dontKill_applicationIsNotKilled() throws Exception {
+        final Intent intent = new Intent();
+        intent.setComponent(STUB_SERVICE_COMPONENT);
+        final AtomicBoolean killed = new AtomicBoolean();
+        mServiceTestRule.bindService(intent, new ServiceConnection() {
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder service) {
+            }
+
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                killed.set(true);
+            }
+        }, Context.BIND_AUTO_CREATE);
+
+        installPackage(STUB_PACKAGE_APK, true /* dontKill */);
+        // The application shouldn't be killed after updating with --dont-kill.
+        assertThrows(AssertionFailedError.class,
+                () -> TestUtils.waitUntil(
+                        "Waiting for the process " + STUB_PACKAGE_NAME + " to die",
+                        10 /* timeoutSecond */, () -> killed.get()));
+    }
 }
diff --git a/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java b/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
index 2578058..1a6d78b 100644
--- a/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ProviderInfoListTest.java
@@ -33,7 +33,8 @@
     public void testApplicationInfoSquashed() throws Exception {
         final PackageManager pm = getContext().getPackageManager();
         final PackageInfo pi = pm.getPackageInfo(PACKAGE_NAME,
-                PackageManager.GET_PROVIDERS | PackageManager.GET_UNINSTALLED_PACKAGES);
+                PackageManager.PackageInfoFlags.of(
+                        PackageManager.GET_PROVIDERS | PackageManager.GET_UNINSTALLED_PACKAGES));
 
         // Make sure the package contains more than 1 providers.
         assertNotNull(pi);
diff --git a/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java b/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
index b99e294..c3a0e83 100644
--- a/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ProviderInfoTest.java
@@ -19,8 +19,8 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.ProviderInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ProviderInfo;
 import android.content.res.XmlResourceParser;
 import android.os.Parcel;
 import android.platform.test.annotations.AppModeFull;
@@ -42,8 +42,10 @@
         // Test ProviderInfo()
         new ProviderInfo();
         // Test other methods
-        ApplicationInfo appInfo = pm.getApplicationInfo(PACKAGE_NAME, 0);
-        List<ProviderInfo> providers = pm.queryContentProviders(PACKAGE_NAME, appInfo.uid, 0);
+        ApplicationInfo appInfo = pm.getApplicationInfo(PACKAGE_NAME,
+                PackageManager.ApplicationInfoFlags.of(0));
+        List<ProviderInfo> providers = pm.queryContentProviders(PACKAGE_NAME, appInfo.uid,
+                PackageManager.ComponentInfoFlags.of(0));
         Iterator<ProviderInfo> providerIterator = providers.iterator();
         ProviderInfo current;
         while (providerIterator.hasNext()) {
@@ -58,7 +60,7 @@
     public void testProviderMetaData() {
         final ProviderInfo info = getContext().getPackageManager()
                 .resolveContentProvider("android.content.cts.fileprovider",
-                        PackageManager.GET_META_DATA);
+                        PackageManager.ComponentInfoFlags.of(PackageManager.GET_META_DATA));
         final XmlResourceParser in = info.loadXmlMetaData(
                 getContext().getPackageManager(), "android.support.FILE_PROVIDER_PATHS");
         try {
diff --git a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
index 5385577..1c2cbad 100644
--- a/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/ResourcesHardeningTest.java
@@ -327,7 +327,7 @@
 
             // Start the test app and indicate which test to run.
             try (pidDetector; finishDetector) {
-                final Intent launchIntent = new Intent(Intent.ACTION_VIEW);
+                final Intent launchIntent = new Intent(Intent.ACTION_MAIN);
                 launchIntent.setClassName(TestUtils.TEST_APP_PACKAGE, TestUtils.TEST_ACTIVITY_NAME);
                 launchIntent.putExtra(TestUtils.TEST_NAME_EXTRA_KEY, mTestName);
                 launchIntent.putExtra(TestUtils.TEST_ASSERT_SUCCESS_EXTRA_KEY, assertSuccess);
diff --git a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
index b617164..14580eb 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptorTest.java
@@ -16,13 +16,6 @@
 
 package android.content.res.cts;
 
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Arrays;
-
 import android.content.res.AssetFileDescriptor;
 import android.os.Bundle;
 import android.os.Parcel;
@@ -30,6 +23,13 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+
 public class AssetFileDescriptorTest extends AndroidTestCase {
     private static final long START_OFFSET = 0;
     private static final long LENGTH = 100;
@@ -114,12 +114,6 @@
         } catch (IOException e) {
             // expect
         }
-        try {
-            mInputStream = mAssetFileDes.createInputStream();
-            fail("Should throw IOException");
-        } catch (IOException e) {
-            // expect
-        }
         mAssetFileDes.close();
         mAssetFileDes = null;
 
@@ -140,12 +134,6 @@
         mInputStream.close();
         mInputStream = null;
         try {
-            mInputStream = mAssetFileDes.createInputStream();
-            fail("Should throw IOException");
-        } catch (IOException e) {
-            // expect
-        }
-        try {
             mOutputStream = mAssetFileDes.createOutputStream();
             fail("Should throw IOException");
         } catch (IOException e) {
diff --git a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
index 58af714..106ee4e 100644
--- a/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
+++ b/tests/tests/content/src/android/content/res/cts/AssetFileDescriptor_AutoCloseInputStreamTest.java
@@ -152,6 +152,24 @@
         assertEquals(FILE_DATA[2], mInput.read());
     }
 
+    public void testTwoFileDescriptorsWorkIndependently() throws IOException {
+        openInput(0, FILE_LENGTH);
+
+        AssetFileDescriptor fd2 = new AssetFileDescriptor(mFd.getParcelFileDescriptor(),
+                0,
+                FILE_LENGTH);
+        AssetFileDescriptor.AutoCloseInputStream input2 =
+                new AssetFileDescriptor.AutoCloseInputStream(fd2);
+
+        input2.skip(2);
+        input2.read();
+
+        for (int i = 0; i < FILE_LENGTH; i++) {
+            assertEquals(FILE_DATA[i], mInput.read());
+        }
+        assertEquals(FILE_END, mInput.read());
+    }
+
     private void openInput(long startOffset, long length)
             throws IOException {
         if (mFd != null) {
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigTest.java b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
index e07164f..b61dfe8 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigTest.java
@@ -258,7 +258,8 @@
         mContext = InstrumentationRegistry.getContext();
         final PackageManager pm = mContext.getPackageManager();
         try {
-            ApplicationInfo appInfo = pm.getApplicationInfo(TEST_PACKAGE, 0);
+            ApplicationInfo appInfo = pm.getApplicationInfo(TEST_PACKAGE,
+                    PackageManager.ApplicationInfoFlags.of(0));
             mTargetSdkVersion = appInfo.targetSdkVersion;
         } catch (NameNotFoundException e) {
             fail("Should be able to find application info for this package");
@@ -704,13 +705,8 @@
     
     @Test
     public void testDensity() throws Exception {
-        // have 32, 240 and the default 160 content.
-        // rule is that closest wins, with down scaling (larger content)
-        // being twice as nice as upscaling.
-        // transition at H/2 * (-1 +/- sqrt(1+8L/H))
-        // SO, X < 49 goes to 32
-        // 49 >= X < 182 goes to 160
-        // X >= 182 goes to 240
+        // Have 32, 240 and the default 160 content.
+        // Rule is that next highest wins.
         TotalConfig config = makeClassicConfig();
         config.setProperty(Properties.DENSITY, 2);
         Resources res = config.getResources();
@@ -728,13 +724,6 @@
         config = makeClassicConfig();
         config.setProperty(Properties.DENSITY, 48);
         res = config.getResources();
-        checkValue(res, R.configVarying.simple, "simple 32dpi");
-        checkValue(res, R.configVarying.bag,
-                R.styleable.TestConfig, new String[]{"bag 32dpi"});
-
-        config = makeClassicConfig();
-        config.setProperty(Properties.DENSITY, 49);
-        res = config.getResources();
         checkValue(res, R.configVarying.simple, "simple default");
         checkValue(res, R.configVarying.bag,
                 R.styleable.TestConfig, new String[]{"bag default"});
@@ -749,13 +738,6 @@
         config = makeClassicConfig();
         config.setProperty(Properties.DENSITY, 181);
         res = config.getResources();
-        checkValue(res, R.configVarying.simple, "simple default");
-        checkValue(res, R.configVarying.bag,
-                R.styleable.TestConfig, new String[]{"bag default"});
-
-        config = makeClassicConfig();
-        config.setProperty(Properties.DENSITY, 182);
-        res = config.getResources();
         checkValue(res, R.configVarying.simple, "simple 240dpi");
         checkValue(res, R.configVarying.bag,
                 R.styleable.TestConfig, new String[]{"bag 240dpi"});
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index bf43777..84a80dd 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -181,6 +181,87 @@
         assertEquals(1, cfg1.compareTo(cfg2));
     }
 
+    public void testGenerateDiff() {
+        Configuration cfg1 = new Configuration();
+        Configuration cfg2 = new Configuration();
+
+        cfg1.fontScale = 2;
+        cfg2.fontScale = 3;
+
+        cfg1.mcc = 2;
+        cfg2.mcc = 3;
+
+        cfg1.mnc = 2;
+        cfg2.mnc = 3;
+
+        cfg1.locale = new Locale("1", "2", "3");
+        cfg1.locale = new Locale("3", "2", "1");
+
+        cfg1.touchscreen = 2;
+        cfg2.touchscreen = 3;
+
+        cfg1.keyboard = 2;
+        cfg2.keyboard = 3;
+
+        cfg1.keyboardHidden = 2;
+        cfg2.keyboardHidden = 3;
+
+        cfg1.navigation = 2;
+        cfg2.navigation = 3;
+
+        cfg1.navigationHidden = 3;
+        cfg2.navigationHidden = 2;
+
+        cfg1.orientation = 3;
+        cfg2.orientation = 2;
+
+        cfg1.screenLayout = Configuration.SCREENLAYOUT_SIZE_NORMAL
+                | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
+                | Configuration.SCREENLAYOUT_LONG_NO
+                | Configuration.SCREENLAYOUT_ROUND_YES;
+        cfg2.screenLayout = Configuration.SCREENLAYOUT_SIZE_LARGE
+                | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR
+                | Configuration.SCREENLAYOUT_LONG_YES
+                | Configuration.SCREENLAYOUT_ROUND_NO;
+
+        cfg1.colorMode = Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_YES
+                | Configuration.COLOR_MODE_HDR_NO;
+        cfg2.colorMode = Configuration.COLOR_MODE_WIDE_COLOR_GAMUT_NO
+                | Configuration.COLOR_MODE_HDR_YES;
+
+        cfg1.uiMode = Configuration.UI_MODE_TYPE_WATCH
+                | Configuration.UI_MODE_NIGHT_NO;
+        cfg2.uiMode = Configuration.UI_MODE_TYPE_NORMAL
+                | Configuration.UI_MODE_NIGHT_YES;
+
+        cfg1.screenWidthDp = 500;
+        cfg2.screenWidthDp = 600;
+
+        cfg1.screenHeightDp = 920;
+        cfg2.screenHeightDp = 900;
+
+        cfg1.smallestScreenWidthDp = 500;
+        cfg2.smallestScreenWidthDp = 600;
+
+        cfg1.densityDpi = 200;
+        cfg2.densityDpi = 220;
+
+        cfg1.assetsSeq = 4;
+        cfg2.assetsSeq = 5;
+
+        cfg1.fontWeightAdjustment = 2;
+        cfg1.fontWeightAdjustment = 3;
+
+        Configuration delta = Configuration.generateDelta(cfg1, cfg2);
+        assertEquals(cfg2, delta);
+
+        delta = Configuration.generateDelta(cfg2, cfg1);
+        assertEquals(cfg1, delta);
+
+        delta = Configuration.generateDelta(cfg1, cfg1);
+        assertEquals(new Configuration(), delta);
+    }
+
     public void testDescribeContents() {
         assertEquals(0, mConfigDefault.describeContents());
     }
diff --git a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 45b2216..99dfe58 100644
--- a/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
@@ -1043,4 +1043,27 @@
         Typeface typeface = mResources.getFont(R.font.sample_downloadable_font);
         assertEquals(typeface, Typeface.create("sans-serif", Typeface.NORMAL));
     }
+
+    public void testThemeCompare() {
+        Resources.Theme t1 = mResources.newTheme();
+        Resources.Theme t2 = mResources.newTheme();
+        assertTrue(t1.equals(t1));
+        assertTrue(t1.equals(t2));
+        assertTrue(t1.hashCode() == t2.hashCode());
+        assertFalse(t1.equals(null));
+        assertFalse(t1.equals(this));
+
+        t1.applyStyle(1, false);
+        assertFalse(t1.equals(t2));
+        assertFalse(t1.hashCode() == t2.hashCode());
+        t2.applyStyle(1, false);
+        assertTrue(t1.equals(t2));
+        assertTrue(t1.hashCode() == t2.hashCode());
+        t1.applyStyle(2, true);
+        assertFalse(t1.hashCode() == t2.hashCode());
+        assertFalse(t1.equals(t2));
+        t2.applyStyle(2, false);
+        assertFalse(t1.equals(t2));
+        assertFalse(t1.hashCode() == t2.hashCode());
+    }
 }
diff --git a/tests/tests/cronet/Android.bp b/tests/tests/cronet/Android.bp
index 947d5c0..fc8ec7f 100644
--- a/tests/tests/cronet/Android.bp
+++ b/tests/tests/cronet/Android.bp
@@ -34,7 +34,7 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-cronet",
     ],
 
 }
diff --git a/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java b/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
index 247039a..31180c0 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/DatabaseStatementTest.java
@@ -90,6 +90,19 @@
     }
 
     @MediumTest
+    public void testExecutePragmaStatement() {
+        SQLiteStatement statement = mDatabase.compileStatement("PRAGMA busy_timeout = 12000");
+        statement.execute();
+        statement.close();
+
+        // Assert connection has busy timeout configured
+        try (Cursor c = mDatabase.rawQuery("PRAGMA busy_timeout;", null)) {
+            assertTrue(c.moveToNext());
+            assertEquals(c.getInt(0), 12000);
+        }
+    }
+
+    @MediumTest
     public void testSimpleQuery() throws Exception {
         mDatabase.execSQL("CREATE TABLE test (num INTEGER NOT NULL, str TEXT NOT NULL);");
         mDatabase.execSQL("INSERT INTO test VALUES (1234, 'hello');");
diff --git a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
index 81d3657..935bd49 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -25,6 +25,7 @@
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.SQLException;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
 import android.database.sqlite.SQLiteCursor;
 import android.database.sqlite.SQLiteCursorDriver;
 import android.database.sqlite.SQLiteDatabase;
@@ -135,6 +136,70 @@
         db.close();
     }
 
+    public void testOpenDatabase_fail_no_path() {
+        CursorFactory factory = MockSQLiteCursor::new;
+        SQLiteDatabase db = null;
+        try {
+            db = SQLiteDatabase.openDatabase("filename.db",
+                    factory, SQLiteDatabase.CREATE_IF_NECESSARY);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            assertTrue(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("Directory not specified in the file path"));
+            assertFalse(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("Unknown reason"));
+        } finally {
+            if (db != null) {
+                db.close();
+            }
+        }
+    }
+
+    public void testOpenDatabase_fail_root_path_create_if_necessary() {
+        CursorFactory factory = MockSQLiteCursor::new;
+        SQLiteDatabase db = null;
+        try {
+            db = SQLiteDatabase.openDatabase("/filename.db",
+                    factory, SQLiteDatabase.CREATE_IF_NECESSARY);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            assertTrue(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains(
+                            "File /filename.db doesn't exist and CREATE_IF_NECESSARY is set"));
+            assertFalse(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("Unknown reason"));
+        } finally {
+            if (db != null) {
+                db.close();
+            }
+        }
+    }
+
+    public void testOpenDatabase_fail_root_path_no_create() {
+        CursorFactory factory = MockSQLiteCursor::new;
+        SQLiteDatabase db = null;
+        try {
+            db = SQLiteDatabase.openDatabase("/filename.db",
+                    factory, 0);
+        } catch (SQLiteCantOpenDatabaseException e) {
+            assertTrue(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("File /filename.db doesn't exist"));
+            assertFalse(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("CREATE_IF_NECESSARY"));
+            assertFalse(
+                    "Wrong exception message: " + e.getMessage(),
+                    e.getMessage().contains("Unknown reason"));
+        } finally {
+            if (db != null) {
+                db.close();
+            }
+        }
+    }
+
     public void testDeleteDatabase() throws IOException {
         File dbFile = new File(mDatabaseDir, "database_test12345678.db");
         File journalFile = new File(dbFile.getPath() + "-journal");
@@ -560,6 +625,16 @@
         }
     }
 
+    public void testExecPerConnectionSQLPragma() {
+        mDatabase.execPerConnectionSQL("PRAGMA busy_timeout = 12000;", null);
+
+        // Assert connection has busy timeout configured
+        try (Cursor c = mDatabase.rawQuery("PRAGMA busy_timeout;", null)) {
+            assertTrue(c.moveToNext());
+            assertEquals(c.getInt(0), 12000);
+        }
+    }
+
     public void testFindEditTable() {
         String tables = "table1 table2 table3";
         assertEquals("table1", SQLiteDatabase.findEditTable(tables));
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
index 6d77ebb..c378f48 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiPermissionTests.java
@@ -66,6 +66,7 @@
         // setters without write permission
         trySetPropertyWithoutWritePermission(violations);
         trySetPropertiesWithoutWritePermission(violations);
+        tryDeletePropertyWithoutWritePermission(violations);
 
         // getters without read permission
         tryGetPropertyWithoutReadPermission(violations);
@@ -120,6 +121,7 @@
         // setters without write permission
         trySetPropertyWithoutWritePermission(violations);
         trySetPropertiesWithoutWritePermission(violations);
+        tryDeletePropertyWithoutWritePermission(violations);
 
         // getters with read permission
         tryGetPropertyWithReadPermission(violations);
@@ -278,6 +280,15 @@
         }
     }
 
+    private void tryDeletePropertyWithoutWritePermission(StringBuilder violations) {
+        try {
+            DeviceConfig.deleteProperty(NAMESPACE, KEY);
+            violations.append("DeviceConfig.deleteProperty() must not be accessible without "
+                    + "WRITE_DEVICE_CONFIG permission.\n");
+        } catch (SecurityException e) {
+        }
+    }
+
     private void tryGetPropertyWithoutReadPermission(StringBuilder violations) {
         try {
             DeviceConfig.getProperty(NAMESPACE, KEY);
diff --git a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
index c1cbbad..cacaa29 100644
--- a/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
+++ b/tests/tests/deviceconfig/src/android/deviceconfig/cts/DeviceConfigApiTests.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 import static org.junit.Assert.fail;
 
@@ -816,6 +817,106 @@
                 + " getProperty() when property is not null", DEFAULT_FLOAT, result, 0.0f);
     }
 
+    @Test
+    public void testDeleteProperty_nullNamespace() {
+        try {
+            DeviceConfig.deleteProperty(null, KEY1);
+            fail("DeviceConfig.deleteProperty() with null namespace must result in "
+                    + "NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDeleteProperty_nullName() {
+        try {
+            DeviceConfig.deleteProperty(NAMESPACE1, null);
+            fail("DeviceConfig.deleteProperty() with null name must result in "
+                    + "NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testDeletePropertyString() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        assertEquals("DeviceConfig.Properties.getString() must return default value if "
+                + "property is deleted", DEFAULT_VALUE,
+                DeviceConfig.getProperties(NAMESPACE1, KEY1).getString(KEY1, DEFAULT_VALUE));
+    }
+
+    @Test
+    public void testDeletePropertyBoolean() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(BOOLEAN_TRUE));
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        assertEquals("DeviceConfig.Properties.getBoolean() must return default value if "
+                        + "property is deleted", BOOLEAN_FALSE,
+                DeviceConfig.getProperties(NAMESPACE1, KEY1).getBoolean(KEY1,
+                        DEFAULT_BOOLEAN_FALSE));
+    }
+
+    @Test
+    public void testDeletePropertyInt() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_INT));
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        assertEquals("DeviceConfig.Properties.getInt() must return default value if "
+                        + "property is deleted", DEFAULT_INT,
+                DeviceConfig.getProperties(NAMESPACE1, KEY1).getInt(KEY1, DEFAULT_INT));
+    }
+
+    @Test
+    public void testDeletePropertyLong() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_LONG));
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        assertEquals("DeviceConfig.Properties.getLong() must return default value if "
+                        + "property is deleted", DEFAULT_LONG,
+                DeviceConfig.getProperties(NAMESPACE1, KEY1).getLong(KEY1, DEFAULT_LONG));
+    }
+
+    @Test
+    public void testDeletePropertyFloat() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, String.valueOf(VALID_FLOAT));
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+        assertEquals("DeviceConfig.Properties.getString() must return default value if "
+                        + "property is deleted", DEFAULT_FLOAT,
+                DeviceConfig.getProperties(NAMESPACE1, KEY1).getFloat(KEY1, DEFAULT_FLOAT), 0.0f);
+    }
+
+    @Test
+    public void testDeleteProperty_withNonExistingProperty() {
+        assertNull(DeviceConfig.getProperty(NAMESPACE1, KEY1));
+        // Test that deletion returns true when the key doesn't exist
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+    }
+
+    @Test
+    public void testDeleteProperty_withUndeletedProperty() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY2, VALUE2);
+        assertEquals(VALUE1, DeviceConfig.getProperty(NAMESPACE1, KEY1));
+        assertEquals(VALUE2, DeviceConfig.getProperty(NAMESPACE1, KEY2));
+        final Properties propertiesBeforeDeletion = DeviceConfig.getProperties(
+                NAMESPACE1, KEY1, KEY2);
+        assertEquals(VALUE1, propertiesBeforeDeletion.getString(KEY1, DEFAULT_VALUE));
+        assertEquals(VALUE2, propertiesBeforeDeletion.getString(KEY2, DEFAULT_VALUE));
+        // Only delete one property, leaving another one undeleted
+        final Properties propertiesAfterDeletion = deletePropertyAndAssertSuccessfulChange(
+                NAMESPACE1, KEY1);
+        final String result = DeviceConfig.getString(NAMESPACE1, KEY1, DEFAULT_VALUE);
+        assertEquals("DeviceConfig.getString() must return default value if property is "
+                + "deleted", DEFAULT_VALUE, result);
+        assertNull("DeviceConfig.getProperty() must return null if property is deleted",
+                DeviceConfig.getProperty(NAMESPACE1, KEY1));
+        assertEquals(VALUE2, DeviceConfig.getProperty(NAMESPACE1, KEY2));
+        assertEquals("DeviceConfig.Properties.getString() must return default value if "
+                + "property is deleted", DEFAULT_VALUE, propertiesAfterDeletion.getString(KEY1,
+                DEFAULT_VALUE));
+        assertEquals(VALUE2, propertiesBeforeDeletion.getString(KEY2, DEFAULT_VALUE));
+    }
+
     /**
      * Test that properties listener is successfully registered and provides callbacks on value
      * change when DeviceConfig.setProperty is called.
@@ -837,6 +938,16 @@
     }
 
     /**
+     * Test that properties listener is successfully registered and provides callbacks on value
+     * change when DeviceConfig.deleteProperty is called.
+     */
+    @Test
+    public void testPropertiesListener_deleteProperty() {
+        setPropertiesAndAssertSuccessfulChange(NAMESPACE1, KEY1, VALUE1);
+        deletePropertyAndAssertSuccessfulChange(NAMESPACE1, KEY1);
+    }
+
+    /**
      * Test that two properties listeners subscribed to the same namespace are successfully
      * registered and unregistered while receiving correct updates in all states.
      */
@@ -1082,6 +1193,26 @@
         return propertiesUpdate.properties;
     }
 
+    private Properties deletePropertyAndAssertSuccessfulChange(String namespace, String name) {
+        final List<PropertyUpdate> receivedUpdates = new ArrayList<>();
+        OnPropertiesChangedListener changeListener = createOnPropertiesChangedListener(receivedUpdates);
+
+        DeviceConfig.addOnPropertiesChangedListener(namespace, EXECUTOR, changeListener);
+
+        assertTrue(DeviceConfig.deleteProperty(namespace, name));
+        assertNull("DeviceConfig.getProperty() must return null if property is deleted",
+                DeviceConfig.getProperty(namespace, name));
+        waitForListenerUpdateOrTimeout(receivedUpdates, 1);
+        DeviceConfig.removeOnPropertiesChangedListener(changeListener);
+
+        assertEquals("Failed to receive update to OnPropertiesChangedListener",
+                receivedUpdates.size(), 1);
+        PropertyUpdate propertiesUpdate = receivedUpdates.get(0);
+        propertiesUpdate.assertEqual(namespace, name, null);
+
+        return propertiesUpdate.properties;
+    }
+
     private void nullifyProperty(String namespace, String key) {
         if (DeviceConfig.getString(namespace, key, null) != null) {
             setPropertiesAndAssertSuccessfulChange(namespace, key, null);
diff --git a/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java b/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java
new file mode 100644
index 0000000..1d05b0f
--- /dev/null
+++ b/tests/tests/display/src/android/display/cts/DefaultDisplayModeTest.java
@@ -0,0 +1,317 @@
+/*
+ * 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.display.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.graphics.Point;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Display;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.compatibility.common.util.DisplayUtil;
+import com.android.compatibility.common.util.FeatureUtil;
+
+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.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class DefaultDisplayModeTest {
+    private final static int DISPLAY_CHANGE_TIMEOUT_SECS = 3;
+
+    private DisplayManager mDisplayManager;
+    private Display mDefaultDisplay;
+    private Display.Mode mOriginalDisplayModeSettings;
+
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            Manifest.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE,
+            Manifest.permission.HDMI_CEC);
+
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        assumeTrue("Need an Android TV device to run this test.", FeatureUtil.isTV());
+        assertTrue("Physical display is expected.", DisplayUtil.isDisplayConnected(context));
+
+        mDisplayManager = context.getSystemService(DisplayManager.class);
+        mDefaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        cacheOriginalUserPreferredModeSetting();
+        mDisplayManager.clearGlobalUserPreferredDisplayMode();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        restoreOriginalDisplayModeSettings();
+    }
+
+    @Test
+    public void testSetUserPreferredDisplayModeThrowsExceptionWithInvalidMode() {
+        assertThrows(
+                "The mode is invalid. Width, height and refresh rate should be positive.",
+                IllegalArgumentException.class,
+                () -> mDisplayManager.setGlobalUserPreferredDisplayMode(
+                        new Display.Mode(-1, 1080, 120.0f)));
+
+        assertThrows(
+                "The mode is invalid. Width, height and refresh rate should be positive.",
+                IllegalArgumentException.class,
+                () -> mDisplayManager.setGlobalUserPreferredDisplayMode(
+                        new Display.Mode(720, 1080, 0.0f)));
+    }
+
+    @Test
+    public void testDisplayChangedOnSetAndClearUserPreferredDisplayMode() throws Exception {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Test set
+        Display.Mode initialDefaultMode = mDefaultDisplay.getDefaultMode();
+
+        Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+        assertNotNull(newDefaultMode);
+        DefaultModeListener listener =
+                new DefaultModeListener(mDefaultDisplay, newDefaultMode.getModeId());
+        Handler handler = new Handler(Looper.getMainLooper());
+        mDisplayManager.registerDisplayListener(listener, handler);
+        try {
+            mDisplayManager.setGlobalUserPreferredDisplayMode(newDefaultMode);
+            assertTrue(listener.await());
+        } finally {
+            mDisplayManager.unregisterDisplayListener(listener);
+        }
+
+        // Test clear
+        listener = new DefaultModeListener(mDefaultDisplay, initialDefaultMode.getModeId());
+        mDisplayManager.registerDisplayListener(listener, handler);
+        try {
+            mDisplayManager.clearGlobalUserPreferredDisplayMode();
+            assertTrue(listener.await());
+        } finally {
+            mDisplayManager.unregisterDisplayListener(listener);
+        }
+    }
+
+    @Test
+    public void testDisplayChangedOnSetAndClearUserPreferredDisplayModeForSpecificDevice()
+            throws Exception {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Test set
+        Display.Mode initialDefaultMode = mDefaultDisplay.getDefaultMode();
+
+        Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+        assertNotNull(newDefaultMode);
+        DefaultModeListener listener =
+                new DefaultModeListener(mDefaultDisplay, newDefaultMode.getModeId());
+        Handler handler = new Handler(Looper.getMainLooper());
+        mDisplayManager.registerDisplayListener(listener, handler);
+        try {
+            mDefaultDisplay.setUserPreferredDisplayMode(newDefaultMode);
+            assertTrue(listener.await());
+        } finally {
+            mDisplayManager.unregisterDisplayListener(listener);
+        }
+
+        // Test clear
+        listener = new DefaultModeListener(mDefaultDisplay, initialDefaultMode.getModeId());
+        mDisplayManager.registerDisplayListener(listener, handler);
+        try {
+            mDefaultDisplay.clearUserPreferredDisplayMode();
+            assertTrue(listener.await());
+        } finally {
+            mDisplayManager.unregisterDisplayListener(listener);
+        }
+    }
+
+    @Test
+    public void testSetUserPreferredDisplayModeForSpecificDevice() {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Set a display mode which is different from default display mode
+        Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+        assertNotNull(newDefaultMode);
+        mDefaultDisplay.setUserPreferredDisplayMode(newDefaultMode);
+        assertTrue(mDefaultDisplay.getUserPreferredDisplayMode()
+                .matches(newDefaultMode.getPhysicalWidth(),
+                        newDefaultMode.getPhysicalHeight(),
+                        newDefaultMode.getRefreshRate()));
+
+        mDefaultDisplay.clearUserPreferredDisplayMode();
+        assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+    }
+
+    @Test
+    public void testSetUserPreferredRefreshRateForSpecificDevice() {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Set a refresh rate which is different from default display refresh rate
+        float refreshRate = findNonDefaultRefreshRate(mDefaultDisplay);
+        assumeTrue("Need two or more refresh rates to exercise switching.", refreshRate != 0.0f);
+
+        mDefaultDisplay.setUserPreferredDisplayMode(
+                new Display.Mode.Builder().setRefreshRate(refreshRate).build());
+        assertNotNull(mDefaultDisplay.getUserPreferredDisplayMode());
+        assertEquals(
+                refreshRate,
+                mDefaultDisplay.getUserPreferredDisplayMode().getRefreshRate(),
+                0.00001 /* delta */);
+
+        mDefaultDisplay.clearUserPreferredDisplayMode();
+        assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+    }
+
+    @Test
+    public void testSetUserPreferredResolutionForSpecificDevice() {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Set a refresh rate which is different from default display refresh rate
+        Point resolution = findNonDefaultResolution(mDefaultDisplay);
+        assumeTrue("Need two or more resolutions to exercise switching.",
+                resolution.x != -1 && resolution.y != -1);
+
+        mDefaultDisplay.setUserPreferredDisplayMode(
+                new Display.Mode.Builder().setResolution(resolution.x, resolution.y).build());
+        assertNotNull(mDefaultDisplay.getUserPreferredDisplayMode());
+        assertEquals(resolution.x,
+                mDefaultDisplay.getUserPreferredDisplayMode().getPhysicalWidth());
+        assertEquals(resolution.y,
+                mDefaultDisplay.getUserPreferredDisplayMode().getPhysicalHeight());
+
+        mDefaultDisplay.clearUserPreferredDisplayMode();
+        assertNull(mDefaultDisplay.getUserPreferredDisplayMode());
+    }
+
+    @Test
+    public void testGetUserPreferredDisplayMode() {
+        Display.Mode[] modes = mDefaultDisplay.getSupportedModes();
+        assumeTrue("Need two or more display modes to exercise switching.", modes.length > 1);
+
+        // Set a display mode which is different from default display mode
+        Display.Mode newDefaultMode = findNonDefaultMode(mDefaultDisplay);
+        assertNotNull(newDefaultMode);
+        mDisplayManager.setGlobalUserPreferredDisplayMode(newDefaultMode);
+        assertTrue(mDisplayManager.getGlobalUserPreferredDisplayMode()
+                .matches(newDefaultMode.getPhysicalWidth(),
+                        newDefaultMode.getPhysicalHeight(),
+                        newDefaultMode.getRefreshRate()));
+
+        mDisplayManager.clearGlobalUserPreferredDisplayMode();
+        assertNull(mDisplayManager.getGlobalUserPreferredDisplayMode());
+    }
+
+    private void cacheOriginalUserPreferredModeSetting() {
+        mOriginalDisplayModeSettings =
+                mDisplayManager.getGlobalUserPreferredDisplayMode();
+    }
+
+    private void restoreOriginalDisplayModeSettings() {
+        // mDisplayManager can be null if the test assumptions if setUp have failed.
+        if (mDisplayManager == null) {
+            return;
+        }
+        if (mOriginalDisplayModeSettings == null) {
+            mDisplayManager.clearGlobalUserPreferredDisplayMode();
+        } else {
+            mDisplayManager.setGlobalUserPreferredDisplayMode(mOriginalDisplayModeSettings);
+        }
+    }
+
+    private Display.Mode findNonDefaultMode(Display display) {
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getModeId() != display.getDefaultMode().getModeId()) {
+                return mode;
+            }
+        }
+        return null;
+    }
+
+    private float findNonDefaultRefreshRate(Display display) {
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getRefreshRate() != display.getDefaultMode().getRefreshRate()) {
+                return mode.getRefreshRate();
+            }
+        }
+        return 0.0f;
+    }
+
+    private Point findNonDefaultResolution(Display display) {
+        for (Display.Mode mode : display.getSupportedModes()) {
+            if (mode.getPhysicalWidth() != display.getDefaultMode().getPhysicalWidth()
+                    || mode.getPhysicalHeight() != display.getDefaultMode().getPhysicalHeight()) {
+                return new Point(mode.getPhysicalWidth(), mode.getPhysicalHeight());
+            }
+        }
+        return new Point(-1, -1);
+    }
+
+    private static class DefaultModeListener implements DisplayManager.DisplayListener {
+        private final Display mDisplay;
+        private final int mAwaitedDefaultModeId;
+        private final CountDownLatch mLatch;
+
+        private DefaultModeListener(Display display, int awaitedDefaultModeId) {
+            mDisplay = display;
+            mAwaitedDefaultModeId = awaitedDefaultModeId;
+            mLatch = new CountDownLatch(1);
+        }
+
+        @Override
+        public void onDisplayAdded(int displayId) {}
+
+        @Override
+        public void onDisplayChanged(int displayId) {
+            if (displayId != mDisplay.getDisplayId()) {
+                return;
+            }
+
+            if (mAwaitedDefaultModeId == mDisplay.getDefaultMode().getModeId()) {
+                mLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onDisplayRemoved(int displayId) {}
+
+        public boolean await() throws InterruptedException {
+            return mLatch.await(DISPLAY_CHANGE_TIMEOUT_SECS, TimeUnit.SECONDS);
+        }
+    };
+}
diff --git a/tests/tests/dreams/Android.bp b/tests/tests/dreams/Android.bp
index 00fc883..acf751f 100644
--- a/tests/tests/dreams/Android.bp
+++ b/tests/tests/dreams/Android.bp
@@ -20,6 +20,7 @@
     name: "CtsDreamsTestCases",
     defaults: ["cts_defaults"],
     static_libs: [
+        "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "junit",
     ],
@@ -36,4 +37,7 @@
         "cts",
         "general-tests",
     ],
+    data: [
+        ":CtsDreamOverlayTestApp",
+    ],
 }
diff --git a/tests/tests/dreams/AndroidTest.xml b/tests/tests/dreams/AndroidTest.xml
index f6bd05a..cc4c9b5 100644
--- a/tests/tests/dreams/AndroidTest.xml
+++ b/tests/tests/dreams/AndroidTest.xml
@@ -23,6 +23,7 @@
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsDreamsTestCases.apk" />
+        <option name="test-file-name" value="CtsDreamOverlayTestApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.dreams.cts" />
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
new file mode 100644
index 0000000..d905df0
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/Android.bp
@@ -0,0 +1,26 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsDreamOverlayTestApp",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "current",
+    srcs: [
+        "src/**/*.java",
+    ],
+}
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..1dc1d07
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.app.dream.cts.app">
+    <application android:label="CtsDreamTestApp">
+        <service
+            android:name=".DreamOverlayService"
+            android:exported="true">
+        </service>
+        <service
+            android:name=".TestDreamService"
+            android:exported="true"
+            android:permission="android.permission.BIND_DREAM_SERVICE">
+        <intent-filter>
+            <action android:name="android.service.dreams.DreamService" />
+            <category android:name="android.intent.category.DEFAULT" />
+        </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.java
new file mode 100644
index 0000000..53c70b3
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/DreamOverlayService.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.app.dream.cts.app;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+/**
+ * {@link DreamOverlayService} provides a test implementation of
+ * {@link android.service.dreams.DreamOverlayService}. When informed of the dream state, the service
+ * populates a child window with a simple view.Once that view's visibility changes, the dream
+ * broadcasts an action that tests wait upon as a signal the overlay has been displayed.
+ */
+public class DreamOverlayService extends android.service.dreams.DreamOverlayService {
+    public static final String ACTION_DREAM_OVERLAY_SHOWN =
+            "android.app.dream.cts.app.action.overlay_shown";
+    public static final String TEST_PACKAGE = "android.dreams.cts";
+
+    @Override
+    public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
+        addWindowOverlay(layoutParams);
+    }
+
+    private void addWindowOverlay(WindowManager.LayoutParams layoutParams) {
+        FrameLayout layout = new FrameLayout(this);
+        layout.setBackgroundColor(Color.YELLOW);
+        layout.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+
+        // Add a listener for when the root layout becomes visible. We use this event to signal the
+        // dream overlay has been shown.
+        layout.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+            if (layout.getVisibility() == View.VISIBLE) {
+                final Intent intent = new Intent();
+                intent.setPackage(TEST_PACKAGE);
+                intent.setAction(ACTION_DREAM_OVERLAY_SHOWN);
+                sendBroadcast(intent);
+                requestExit();
+            }
+        });
+
+        final WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
+        wm.addView(layout, layoutParams);
+    }
+}
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java
new file mode 100644
index 0000000..0508b00
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestDreamService.java
@@ -0,0 +1,37 @@
+/*
+ * 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.app.dream.cts.app;
+
+import android.graphics.Color;
+import android.service.dreams.DreamService;
+import android.widget.FrameLayout;
+
+/**
+ * {@link TestDreamService} is a minimal concrete {@link DreamService} implementation that sets
+ * the entire window to be blue.
+ */
+public class TestDreamService extends DreamService {
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        setInteractive(false);
+        setFullscreen(true);
+
+        final FrameLayout frameLayout = new FrameLayout(getApplicationContext());
+        frameLayout.setBackgroundColor(Color.BLUE);
+        setContentView(frameLayout);
+    }
+}
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
new file mode 100644
index 0000000..be82ebf
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
@@ -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.
+ */
+
+package android.service.dreams.cts;
+
+import android.Manifest;
+import android.app.DreamManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ServiceManager;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+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.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DreamOverlayTest  {
+    private static final String DREAM_OVERLAY_SERVICE_COMPONENT =
+            "android.app.dream.cts.app/.DreamOverlayService";
+    private static final String DREAM_SERVICE_COMPONENT =
+            "android.app.dream.cts.app/.TestDreamService";
+    private static final String ACTION_DREAM_OVERLAY_SHOWN =
+            "android.app.dream.cts.app.action.overlay_shown";
+
+    private static final int TIMEOUT_SECONDS = 5;
+
+    private static final ComponentName DREAM_COMPONENT_NAME = ComponentName.unflattenFromString(
+            DREAM_SERVICE_COMPONENT);
+
+    private DreamManager mDreamManager;
+    /**
+     * A simple {@link BroadcastReceiver} implementation that counts down a
+     * {@link CountDownLatch} when a matching message is received
+     */
+    static final class OverlayVisibilityReceiver extends BroadcastReceiver {
+        final CountDownLatch mLatch;
+
+        OverlayVisibilityReceiver(CountDownLatch latch) {
+            mLatch = latch;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mLatch.countDown();
+        }
+    }
+
+    @Rule
+    public AdoptShellPermissionsRule mAdoptShellPermissionsRule = new AdoptShellPermissionsRule(
+            InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+            Manifest.permission.INTERACT_ACROSS_USERS,  Manifest.permission.WRITE_DREAM_STATE);
+
+    @Before
+    public void setup() throws ServiceManager.ServiceNotFoundException {
+        mDreamManager = new DreamManager(InstrumentationRegistry.getTargetContext());
+
+        mDreamManager.setActiveDream(DREAM_COMPONENT_NAME);
+
+        // Register the overlay service.
+        mDreamManager.setDreamOverlay(ComponentName.unflattenFromString(
+                DREAM_OVERLAY_SERVICE_COMPONENT));
+    }
+
+    @After
+    public void teardown() {
+        mDreamManager.setActiveDream(null);
+
+        // Unregister overlay service.
+        mDreamManager.setDreamOverlay(null);
+    }
+
+    @Test
+    public void testDreamOverlayAppearance() throws Exception {
+        // Listen for the overlay to be shown
+        final CountDownLatch countDownLatch = new CountDownLatch(1);
+        InstrumentationRegistry.getTargetContext().registerReceiver(
+                new OverlayVisibilityReceiver(countDownLatch),
+                new IntentFilter(ACTION_DREAM_OVERLAY_SHOWN));
+
+        mDreamManager.startDream(DREAM_COMPONENT_NAME);
+
+        // Wait on count down latch.
+        assert (countDownLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
+    }
+}
diff --git a/tests/tests/gamemanager/AndroidManifest.xml b/tests/tests/gamemanager/AndroidManifest.xml
index ca874b9..434f73d 100644
--- a/tests/tests/gamemanager/AndroidManifest.xml
+++ b/tests/tests/gamemanager/AndroidManifest.xml
@@ -20,11 +20,16 @@
 
     <application android:appCategory="game">
         <uses-library android:name="android.test.runner"/>
+        <meta-data android:name="com.android.app.gamemode.performance.enabled"
+                   android:value="true"/>
+        <meta-data android:name="com.android.app.gamemode.battery.enabled"
+                   android:value="true"/>
         <activity android:name=".GameManagerCtsActivity"
                   android:label="GameManagerCtsActivity"
                   android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
diff --git a/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
index 2ad0f22..1afdc7e 100644
--- a/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
+++ b/tests/tests/gamemanager/src/android/gamemanager/cts/GameManagerTest.java
@@ -16,35 +16,45 @@
 
 package android.gamemanager.cts;
 
-import static com.google.common.truth.Truth.assertThat;
-
-import android.app.GameManager;
-import android.content.Context;
-import android.util.Log;
-
-import androidx.test.ext.junit.rules.ActivityScenarioRule;
-
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
+import static org.junit.Assume.assumeTrue;
+
+import android.app.GameManager;
+import android.app.GameState;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.support.test.uiautomator.UiDevice;
+
 import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
-import com.android.compatibility.common.util.SystemUtil;
 
-import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@RunWith(AndroidJUnit4.class)
 public class GameManagerTest {
     private static final String TAG = "GameManagerTest";
+    private static final String GAME_OVERLAY_FEATURE_NAME =
+            "com.google.android.feature.GAME_OVERLAY";
+    private static final String POWER_DUMPSYS_CMD = "dumpsys android.hardware.power.IPower/default";
+    private static final Pattern GAME_LOADING_REGEX =
+            Pattern.compile("^GAME_LOADING\\t(\\d*)\\t\\d*$", Pattern.MULTILINE);
 
     private GameManagerCtsActivity mActivity;
     private Context mContext;
     private GameManager mGameManager;
+    private UiDevice mUiDevice;
 
     @Rule
     public ActivityScenarioRule<GameManagerCtsActivity> mActivityRule =
@@ -56,8 +66,10 @@
             mActivity = activity;
         });
 
-        mContext = getInstrumentation().getContext();
+        final Instrumentation instrumentation = getInstrumentation();
+        mContext = instrumentation.getContext();
         mGameManager = mContext.getSystemService(GameManager.class);
+        mUiDevice = UiDevice.getInstance(instrumentation);
     }
 
     /**
@@ -66,6 +78,9 @@
      */
     @Test
     public void testGetGameModeUnsupported() {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
                 (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
                         GameManager.GAME_MODE_UNSUPPORTED));
@@ -82,6 +97,9 @@
      */
     @Test
     public void testGetGameModeStandard() {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
                 (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
                         GameManager.GAME_MODE_STANDARD));
@@ -98,6 +116,9 @@
      */
     @Test
     public void testGetGameModePerformance() {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
                 (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
                         GameManager.GAME_MODE_PERFORMANCE));
@@ -114,6 +135,9 @@
      */
     @Test
     public void testGetGameModeBattery() {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(GAME_OVERLAY_FEATURE_NAME));
+
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
                 (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
                         GameManager.GAME_MODE_BATTERY));
@@ -123,4 +147,43 @@
         Assert.assertEquals("Game Manager returned incorrect value.",
                 GameManager.GAME_MODE_BATTERY, gameMode);
     }
+
+    private int getGameLoadingCount() throws IOException {
+        final Matcher matcher =
+                GAME_LOADING_REGEX.matcher(mUiDevice.executeShellCommand(POWER_DUMPSYS_CMD));
+        assumeTrue(matcher.find());
+        return Integer.parseInt(matcher.group(1));
+    }
+
+    /**
+     * Test that GameManager::setGameContext() with an 'isLoading' context does not invokes the mode
+     * on the PowerHAL when performance mode is not invoked.
+     */
+    @Test
+    public void testSetGameContextStandardMode() throws IOException, InterruptedException {
+        final int gameLoadingCountBefore = getGameLoadingCount();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
+                (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
+                GameManager.GAME_MODE_STANDARD));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager, (gameManager) ->
+                gameManager.setGameState(new GameState(true, GameState.MODE_NONE)));
+        Thread.sleep(500);  // Wait for change to take effect.
+        Assert.assertEquals(gameLoadingCountBefore, getGameLoadingCount());
+    }
+
+    /**
+     * Test that GameManager::setGameContext() with an 'isLoading' context actually invokes the mode
+     * on the PowerHAL when performance mode is invoked.
+     */
+    @Test
+    public void testSetGameContextPerformanceMode() throws IOException, InterruptedException {
+        final int gameLoadingCountBefore = getGameLoadingCount();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager,
+                (gameManager) -> gameManager.setGameMode(mActivity.getPackageName(),
+                GameManager.GAME_MODE_PERFORMANCE));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mGameManager, (gameManager) ->
+                gameManager.setGameState(new GameState(true, GameState.MODE_NONE)));
+        Thread.sleep(500);  // Wait for change to take effect.
+        Assert.assertEquals(gameLoadingCountBefore + 1, getGameLoadingCount());
+    }
 }
diff --git a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
index 20ca0e7..0badb7c 100644
--- a/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
+++ b/tests/tests/graphics/assets/fonts/layout/hyphenation.ttx
@@ -157,6 +157,66 @@
         <map code="0x0079" name="1em" /> <!-- y -->
         <map code="0x007A" name="1em" /> <!-- z -->
 
+        <map code="0X00ED" name="1em" /> <!-- á -->
+        <map code="0X00ED" name="1em" /> <!-- í -->
+
+        <map code="0X00F6" name="1em" /> <!-- ö -->
+
+        <map code="0X0101" name="1em" /> <!-- ā -->
+
+        <map code="0X016B" name="1em" /> <!-- ū -->
+
+        <map code="0X03B1" name="1em" /> <!-- α -->
+        <map code="0X03B3" name="1em" /> <!-- γ -->
+        <map code="0X03B5" name="1em" /> <!-- ε -->
+        <map code="0X03B6" name="1em" /> <!-- ζ -->
+        <map code="0X03BC" name="1em" /> <!-- μ -->
+        <map code="0X03BD" name="1em" /> <!-- ν -->
+        <map code="0X03BF" name="1em" /> <!-- ο -->
+        <map code="0X03C1" name="1em" /> <!-- ρ -->
+        <map code="0X03C2" name="1em" /> <!-- ς -->
+        <map code="0X03C5" name="1em" /> <!-- υ -->
+        <map code="0X03CC" name="1em" /> <!-- ό -->
+
+        <map code="0X0432" name="1em" /> <!-- в -->
+        <map code="0X043A" name="1em" /> <!-- к -->
+        <map code="0X043C" name="1em" /> <!-- м -->
+        <map code="0X043D" name="1em" /> <!-- н -->
+        <map code="0X0442" name="1em" /> <!-- т -->
+        <map code="0X044C" name="1em" /> <!-- ь -->
+        <map code="0X0456" name="1em" /> <!-- і -->
+
+        <map code="0X0423" name="1em" /> <!-- У -->
+        <map code="0X0432" name="1em" /> <!-- в -->
+        <map code="0X0435" name="1em" /> <!-- е -->
+        <map code="0X0437" name="1em" /> <!-- з -->
+        <map code="0X043A" name="1em" /> <!-- к -->
+        <map code="0X043C" name="1em" /> <!-- м -->
+        <map code="0X043D" name="1em" /> <!-- н -->
+        <map code="0X043E" name="1em" /> <!-- о -->
+        <map code="0X0441" name="1em" /> <!-- с -->
+        <map code="0X0442" name="1em" /> <!-- т -->
+        <map code="0X044C" name="1em" /> <!-- ь -->
+        <map code="0X0456" name="1em" /> <!-- і -->
+
+        <map code="0X10D0" name="1em" /> <!-- ა -->
+        <map code="0X10D7" name="1em" /> <!-- თ -->
+        <map code="0X10D8" name="1em" /> <!-- ი -->
+        <map code="0X10DB" name="1em" /> <!-- მ -->
+        <map code="0X10DC" name="1em" /> <!-- ნ -->
+        <map code="0X10DD" name="1em" /> <!-- ო -->
+        <map code="0X10E0" name="1em" /> <!-- რ -->
+        <map code="0X10E4" name="1em" /> <!-- ფ -->
+        <map code="0X10EA" name="1em" /> <!-- ც -->
+
+        <map code="0x1218" name="1em" /> <!-- መ -->
+        <map code="0x1260" name="1em" /> <!-- በ -->
+        <map code="0x1278" name="1em" /> <!-- ቸ -->
+        <map code="0x1295" name="1em" /> <!-- ን -->
+        <map code="0x12CD" name="1em" /> <!-- ው -->
+        <map code="0x12DB" name="1em" /> <!-- ዛ -->
+        <map code="0x130B" name="1em" /> <!-- ጋ -->
+
         <map code="0x2010" name="2em" /> <!-- HYPHEN -->
         <map code="0x058A" name="3em" /> <!-- ARMENIAN HYPHEN -->
         <map code="0x05BE" name="4em" /> <!-- MAQAF -->
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttf b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
index eb18c0a..2638ae0 100644
--- a/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttf
Binary files differ
diff --git a/tests/tests/graphics/assets/fonts/layout/linebreak.ttx b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
index 18a82a9..f8759b9 100644
--- a/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
+++ b/tests/tests/graphics/assets/fonts/layout/linebreak.ttx
@@ -179,6 +179,7 @@
         <map code="0x0078" name="1em" /> <!-- x -->
         <map code="0x0079" name="1em" /> <!-- y -->
         <map code="0x007A" name="1em" /> <!-- z -->
+        <map code="0x2010" name="1em" /> <!-- hyphen -->
     </cmap_format_4>
   </cmap>
 
diff --git a/tests/tests/graphics/jni/VulkanTestHelpers.cpp b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
index ea16aea..89049e0 100644
--- a/tests/tests/graphics/jni/VulkanTestHelpers.cpp
+++ b/tests/tests/graphics/jni/VulkanTestHelpers.cpp
@@ -121,6 +121,18 @@
   ASSERT(status == VK_SUCCESS || status == VK_INCOMPLETE);
   ASSERT(gpuCount > 0);
 
+  VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures = {
+      .sType =
+          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR,
+      .pNext = nullptr,
+  };
+  VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures = {
+      .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
+      .pNext = &ycbcrFeatures,
+  };
+  vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
+  ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
+
   VkPhysicalDeviceProperties physicalDeviceProperties;
   vkGetPhysicalDeviceProperties(mGpu, &physicalDeviceProperties);
   std::vector<const char *> deviceExt;
@@ -182,7 +194,7 @@
 
   VkDeviceCreateInfo deviceCreateInfo{
       .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
-      .pNext = nullptr,
+      .pNext = &ycbcrFeatures,
       .queueCreateInfoCount = 1,
       .pQueueCreateInfos = &queueCreateInfo,
       .enabledLayerCount = 0,
@@ -226,18 +238,6 @@
           (PFN_vkImportSemaphoreFdKHR)vkGetDeviceProcAddr(mDevice, "vkImportSemaphoreFdKHR");
   ASSERT(mPfnImportSemaphoreFd);
 
-  VkPhysicalDeviceSamplerYcbcrConversionFeaturesKHR ycbcrFeatures{
-      .sType =
-          VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SAMPLER_YCBCR_CONVERSION_FEATURES_KHR,
-      .pNext = nullptr,
-  };
-  VkPhysicalDeviceFeatures2KHR physicalDeviceFeatures{
-      .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2_KHR,
-      .pNext = &ycbcrFeatures,
-  };
-  vkGetPhysicalDeviceFeatures2(mGpu, &physicalDeviceFeatures);
-  ASSERT(ycbcrFeatures.samplerYcbcrConversion == VK_TRUE);
-
   vkGetDeviceQueue(mDevice, 0, 0, &mQueue);
   vkGetPhysicalDeviceMemoryProperties(mGpu, &mMemoryProperties);
 
diff --git a/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml b/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml
new file mode 100644
index 0000000..28f3b20
--- /dev/null
+++ b/tests/tests/graphics/res/color/colorPrimaryDark_csl_with_theme_attr.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="?attr/colorPrimaryDark"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml b/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml
new file mode 100644
index 0000000..cccd6dd
--- /dev/null
+++ b/tests/tests/graphics/res/color/colorPrimary_csl_with_theme_attr.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2018 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:color="?attr/colorPrimary"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
index 2663f3c..7c51385 100644
--- a/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
+++ b/tests/tests/graphics/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
Binary files differ
diff --git a/tests/tests/graphics/res/drawable/adaptiveicondrawable.xml b/tests/tests/graphics/res/drawable/adaptiveicondrawable.xml
new file mode 100644
index 0000000..1ffe9d5
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/adaptiveicondrawable.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.
+ -->
+
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+    <background android:drawable="@android:color/white"/>
+    <foreground android:drawable="@android:color/black"/>
+    <monochrome android:drawable="@android:color/system_accent2_800"/>
+</adaptive-icon>
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml
new file mode 100644
index 0000000..75cdb82
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_color_all_theme.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="@color/colorPrimary_csl_with_theme_attr"
+        android:centerColor="@color/colorPrimaryDark_csl_with_theme_attr"
+        android:endColor="@color/colorPrimaryDark_csl_with_theme_attr"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml
new file mode 100644
index 0000000..db2d2fc
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_color_mix_theme.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="?attr/colorAccent"
+        android:centerColor="@color/colorPrimary_csl_with_theme_attr"
+        android:endColor="?attr/colorPrimaryDark"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml b/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml
new file mode 100644
index 0000000..63115ae
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/gradientdrawable_noCenterColor_all_theme.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="@color/colorPrimary_csl_with_theme_attr"
+        android:endColor="@color/colorPrimaryDark_csl_with_theme_attr"/>
+</shape>
\ No newline at end of file
diff --git a/tests/tests/graphics/res/xml/valid_themes.xml b/tests/tests/graphics/res/xml/valid_themes.xml
new file mode 100644
index 0000000..f693be1
--- /dev/null
+++ b/tests/tests/graphics/res/xml/valid_themes.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<themes>
+    <theme color="ffb9567a">
+        <spritz>ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000</spritz>
+        <tonal_spot>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5d78,904660,733049,581932,3d031e,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000,ffffff,fffbfa,ffeddf,ffdcbf,efbc94,d1a27a,b48762,976d4a,7c5635,623f20,48290b,2f1500,000000,ffffff,fffbfa,faeeef,ebe0e1,cfc4c5,b3a8aa,988f90,7d7475,655c5e,4c4546,352f30,201a1c,000000,ffffff,fffbfa,ffecf1,f2dee2,d5c2c6,b9a6aa,9e8c90,827276,6a5a5e,514347,392d30,23191b,000000</tonal_spot>
+        <vibrant>ffffff,fffbfa,ffecf1,ffd9e4,ffb0ca,e992ae,cb7793,ac5d78,904660,733049,581932,3d031e,000000,ffffff,fffbfa,ffedef,ffdadf,f3b7bf,d59ca3,b88389,9a696f,805158,653b41,4b252b,321017,000000,ffffff,fffbfa,ffecea,ffdad7,ffb3b1,ef918f,d17776,b15e5c,944747,763031,591a1c,3d050a,000000,ffffff,fffbfa,ffecf1,f2dee2,d5c2c6,b9a6aa,9e8c90,827276,6a5a5e,514347,392d30,23191b,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000</vibrant>
+        <expressive>ffffff,fffbfb,ffeaff,f9d7ff,e7b4f9,ca9adc,ae80c0,9265a4,784e8a,5f3670,471f58,2f0641,000000,ffffff,fffbfa,ffecea,ffdad7,f5b7b5,d79d9a,ba8281,9c6867,815150,663b3a,4c2524,331111,000000,ffffff,fefbff,efefff,dde0ff,bac3ff,9da6ec,838cd0,6872b3,50599a,384180,212a68,081253,000000,ffffff,fffbfa,ffecf1,ffd9e2,e2bdc6,c5a2ab,a98891,8d6e76,74565f,5b3f47,422a31,2b151c,000000,ffffff,fffbfa,ffecf1,ffd9e4,fcb2c8,de98ad,c07e92,a26378,874c60,6b3648,511f32,370b1d,000000</expressive>
+    </theme>
+    <theme color="ffb16307">
+        <spritz>ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000</spritz>
+        <tonal_spot>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,e59b57,c78140,a76729,8b5011,6d3900,4d2700,2f1500,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000,ffffff,fdffd7,eef5be,e0e8b1,c4cb97,a8af7e,8e9565,727a4d,5b6238,444a22,2d330e,191e00,000000,ffffff,fffbfa,faeee8,ece0da,cfc4be,b3a9a3,988f89,7d746f,655d58,4c4641,362f2b,201a16,000000,ffffff,fffbfa,ffede0,f3dfd1,d5c3b6,baa89c,9e8e82,837368,6a5b51,51443b,3a2e26,231a12,000000</tonal_spot>
+        <vibrant>ffffff,fffbfa,ffeddf,ffdcbf,ffb776,e59b57,c78140,a76729,8b5011,6d3900,4d2700,2f1500,000000,ffffff,fffbf9,ffeedc,ffddb6,e9bf8f,cca476,b08a5e,937046,795831,5e411b,442b07,2b1700,000000,ffffff,fffbf9,ffeed5,ffdea8,f3be61,d4a349,b78932,9a6e18,7e5700,604100,432c00,281900,000000,ffffff,fffbfa,ffede0,f3dfd1,d5c3b6,baa89c,9e8e82,837368,6a5b51,51443b,3a2e26,231a12,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000</vibrant>
+        <expressive>ffffff,fffbfa,ffecea,ffdad7,ffb3b1,f0918f,d17876,b05e5d,944747,763031,591a1c,3d050a,000000,ffffff,fffbf9,ffeed5,ffdea8,e3c28c,c6a774,aa8c5c,8d7243,745a2f,5a4319,412d05,281900,000000,ffffff,fffbfa,ffebf7,ffd7f2,fab0e5,dc95c8,bf7bad,a16191,864978,6b325f,511b47,380330,000000,ffffff,fffbfa,ffeddf,ffdcc1,e2c0a5,c5a58b,aa8b72,8d705a,745943,5a422d,412c19,2a1707,000000,ffffff,fffbfa,ffeddf,ffdcbf,fab982,dc9e6a,be8452,9f6a3a,845326,683c10,4d2700,2f1500,000000</expressive>
+    </theme>
+    <theme color="ff6e7e0f">
+        <spritz>ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000</spritz>
+        <tonal_spot>ffffff,fdffd7,eaf99a,dbeb8d,c0ce75,a4b35c,8a9844,707c2d,586416,404c00,2b3400,181e00,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000,ffffff,f0fffa,cbfbed,bdeddf,a1d0c3,87b4a8,6d9a8e,527e73,3b665c,224e45,05372f,002019,000000,ffffff,fffcf4,f3f1e8,e5e3da,c8c7bf,adaba4,929189,777670,5f5f58,474741,30312b,1c1c17,000000,ffffff,fefdec,f2f2e0,e4e4d3,c7c7b7,acac9d,919283,767769,5e6052,46483b,303126,1b1c12,000000</tonal_spot>
+        <vibrant>ffffff,fdffd7,eaf99a,dbeb8d,c0ce75,a4b35c,8a9844,707c2d,586416,404c00,2b3400,181e00,000000,ffffff,f9ffe2,e6f7c5,d8e9b8,bbcd9d,a1b184,86976b,6c7c52,54633d,3d4b27,273513,131f02,000000,ffffff,f6ffe8,ccffb3,bff0a6,a3d48c,89b873,6f9c5b,568143,3e692e,275018,103803,022100,000000,ffffff,fefdec,f2f2e0,e4e4d3,c7c7b7,acac9d,919283,767769,5e6052,46483b,303126,1b1c12,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000</vibrant>
+        <expressive>ffffff,fffbf9,ffeed5,ffdea8,f3be61,d5a349,b78832,996e18,7e5700,604100,432c00,281900,000000,ffffff,f6ffe8,def9cd,d0eabf,b5cea5,99b38b,7f9872,657d59,4e6543,374d2d,213618,0c2006,000000,ffffff,fffbfa,ffede6,ffdbcd,ffb597,ef9471,cf7a59,af6141,924a2c,753318,571e04,380d00,000000,ffffff,fcffdc,f0f3d0,e2e5c2,c6c9a8,aaae8e,909375,74795b,5c6145,454930,2f321b,1a1d08,000000,ffffff,fcffd4,ecf8ad,dee99f,c1cd86,a7b16d,8c9656,717b3e,5a6328,424b12,2c3400,181e00,000000</expressive>
+    </theme>
+    <theme color="ff008772">
+        <spritz>ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000</spritz>
+        <tonal_spot>ffffff,f0fffa,b3ffec,96f4dc,79d8c0,5cbca5,3fa08b,188571,006b59,005143,00382d,002019,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000,ffffff,fafcff,e4f3ff,c6e7ff,aacae3,8fafc8,7594ac,5a7a90,426278,2a4a5f,103447,001e2f,000000,ffffff,fafdfa,eff1ef,e0e3e0,c4c7c5,a8aca9,8e918f,737775,5c5f5d,444846,2e3130,191c1b,000000,ffffff,f4fefa,e9f3ef,dae5e0,bec9c4,a3ada9,88938f,6e7875,57615d,3f4945,29322f,141d1b,000000</tonal_spot>
+        <vibrant>ffffff,f0fffa,b3ffec,96f4dc,79d8c0,5cbca5,3fa08b,188571,006b59,005143,00382d,002019,000000,ffffff,effffd,cafbf4,bbece5,a0d0c9,86b4ae,6b9994,517e79,396661,1f4e4a,013733,00201d,000000,ffffff,efffff,acffff,8ff2f5,73d6d9,55babd,339fa2,008386,00696c,004f51,003739,002021,000000,ffffff,f4fefa,e9f3ef,dae5e0,bec9c4,a3ada9,88938f,6e7875,57615d,3f4945,29322f,141d1b,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000</vibrant>
+        <expressive>ffffff,f6ffe8,ceffb2,c0f0a5,a5d38b,8ab872,709c5a,568142,40682d,285017,123802,042100,000000,ffffff,efffff,c9fafa,bcebec,a0cfd0,85b4b4,6b9999,507e7e,386667,1e4e4f,003738,002021,000000,ffffff,fffbf7,fff1b1,fae27c,dcc764,bfab4c,a49034,87751a,6e5e00,544600,3a3000,221b00,000000,ffffff,f0fffa,dbf7ed,cde9df,b1ccc3,96b1a8,7d968e,627b74,4b635c,334b45,1c352e,06201a,000000,ffffff,f0fffa,b9ffeb,abf0dd,8fd4c1,74b8a6,599d8c,3e8272,23695b,005143,00382d,002019,000000</expressive>
+    </theme>
+    <theme color="ff007fb6">
+        <spritz>ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000</spritz>
+        <tonal_spot>ffffff,fafcff,e4f2ff,c7e6ff,8bcefd,70b2e1,5497c5,357ca8,10648f,004b6f,00344f,001e30,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000,ffffff,fffbfd,f7edff,ebdcff,cfc0e9,b2a5cc,978bb0,7c7095,64597b,4c4163,352b4a,201634,000000,ffffff,fbfcff,f0f0f3,e2e2e5,c6c6c9,aaabae,909194,757679,5c5e61,454749,2f3032,191c1e,000000,ffffff,fafcff,ecf1f9,dee3ea,c2c7ce,a6acb2,8b9198,70777d,595f65,41474d,2b3137,171c21,000000</tonal_spot>
+        <vibrant>ffffff,fafcff,e4f2ff,c7e6ff,8bcefd,70b2e1,5497c5,357ca8,10648f,004b6f,00344f,001e30,000000,ffffff,fcfcff,e8f1ff,cfe5ff,b0c9e7,95aecb,7b93af,607894,48607b,304962,19324b,001d34,000000,ffffff,fdfcff,ebf1ff,d4e3ff,a6c8ff,87adea,6c92cd,5177b1,375f97,1b477d,003063,001b3d,000000,ffffff,fafcff,ecf1f9,dee3ea,c2c7ce,a6acb2,8b9198,70777d,595f65,41474d,2b3137,171c21,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000</vibrant>
+        <expressive>ffffff,efffff,b4fdff,8ff3f7,72d6da,54babf,339fa3,008388,00696e,004f53,00373a,002022,000000,ffffff,fdfcff,ebf1ff,d4e3ff,b5c7ea,9aaccc,8092b1,657795,4d5f7c,364764,1f314c,071c36,000000,ffffff,f2fff4,c0ffd9,a4f3c4,88d7a9,6dbb8f,539f76,36845c,196b46,005230,003920,002110,000000,ffffff,fafcff,e4f2ff,d3e5f5,b7c9d8,9caebd,8193a1,677887,50606e,384956,22323f,0c1d29,000000,ffffff,fafcff,e4f2ff,c7e6ff,9dccf0,81b0d4,6796b8,4c7b9c,326383,154b6a,00344f,001e30,000000</expressive>
+    </theme>
+    <theme color="ff8267c2">
+        <spritz>ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000</spritz>
+        <tonal_spot>ffffff,fffbfd,f7edff,ebddff,d2bcff,b5a0e8,9a86cc,7f6bae,675395,4e3b7c,372463,220b4e,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000,ffffff,fffbfa,ffecf0,ffd8e3,f0b7c7,d29dab,b68391,986976,7e525f,633b48,4a2531,31101c,000000,ffffff,fffbfd,f5eff4,e6e1e5,c9c5c9,aeaaae,939094,787579,605d62,484649,313033,1c1b1e,000000,ffffff,fffbfd,f5eefa,e7e0eb,cbc4cf,afa9b4,948f99,79747e,615c66,49454f,322f37,1d1a22,000000</tonal_spot>
+        <vibrant>ffffff,fffbfd,f7edff,ebddff,d2bcff,b5a0e8,9a86cc,7f6bae,675395,4e3b7c,372463,220b4e,000000,ffffff,fffbfb,faecff,f1daff,d4bfe6,b8a4ca,9d89ae,816f92,695779,504060,392a48,241532,000000,ffffff,fffbfb,ffeaff,fad8ff,e8b4f9,cb99dc,af7fc0,9265a3,794e8a,5f3670,471e58,2f0641,000000,ffffff,fffbfd,f5eefa,e7e0eb,cbc4cf,afa9b4,948f99,79747e,615c66,49454f,322f37,1d1a22,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000</vibrant>
+        <expressive>ffffff,fdfcff,ebf1ff,d3e3ff,a4c8ff,86ade9,6b92cd,5077b1,375f97,1a477e,003062,001b3c,000000,ffffff,fffbfb,ffeaff,f8d8ff,dbbce2,bea2c5,a388aa,876d8f,6e5675,553e5d,3e2846,27132f,000000,ffffff,f7fdff,d9f5ff,afecff,79d2ee,5cb7d1,3d9cb6,118199,00677d,004e5f,003542,001f28,000000,ffffff,fffbfd,f7edff,e9def8,ccc3dc,b1a7c0,958da4,7a7389,625b71,4a4358,332d41,1e182b,000000,ffffff,fffbfd,f7edff,ebddff,cfbef6,b4a2d9,9988be,7d6ea1,655788,4d3f6e,362856,211240,000000</expressive>
+    </theme>
+</themes>
\ No newline at end of file
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
index 5b3dedf..2c2ff6d 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapShaderTest.java
@@ -22,12 +22,15 @@
 import android.graphics.BitmapShader;
 import android.graphics.Canvas;
 import android.graphics.Color;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Shader;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.ColorUtils;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -162,4 +165,47 @@
         Assert.assertArrayEquals(new int[] { Color.RED, Color.BLUE, Color.BLUE, Color.RED },
                 pixels);
     }
+
+    @Test
+    public void testFiltering() {
+        Bitmap bitmap = Bitmap.createBitmap(2, 2, Config.ARGB_8888);
+        bitmap.setPixel(0, 0, Color.RED);
+        bitmap.setPixel(1, 0, Color.BLUE);
+        bitmap.setPixel(0, 1, Color.BLUE);
+        bitmap.setPixel(1, 1, Color.RED);
+
+        BitmapShader shader = new BitmapShader(bitmap,
+                Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
+        Assert.assertEquals(BitmapShader.FILTER_MODE_DEFAULT, shader.getFilterMode());
+
+        // use slightly left than half to avoid any confusion on which pixel
+        // is sampled with FILTER_MODE_NEAREST
+        Matrix matrix = new Matrix();
+        matrix.postScale(0.49f, 0.49f);
+        shader.setLocalMatrix(matrix);
+
+        Bitmap dstBitmap = Bitmap.createBitmap(1, 1, Config.ARGB_8888);
+        Canvas canvas = new Canvas(dstBitmap);
+        Paint paint = new Paint();
+        paint.setShader(shader);
+
+        paint.setFilterBitmap(false);
+        canvas.drawPaint(paint);
+        ColorUtils.verifyColor("expected solid red color", Color.RED, dstBitmap.getPixel(0, 0), 0);
+
+        paint.setFilterBitmap(true);
+        canvas.drawPaint(paint);
+        ColorUtils.verifyColor("color should be a blue/red mix", Color.valueOf(0.5f, 0.0f, 0.5f),
+                dstBitmap.getColor(0, 0), 0.05f);
+
+        shader.setFilterMode(BitmapShader.FILTER_MODE_NEAREST);
+        canvas.drawPaint(paint);
+        ColorUtils.verifyColor("expected solid red color", Color.RED, dstBitmap.getPixel(0, 0), 0);
+
+        shader.setFilterMode(BitmapShader.FILTER_MODE_LINEAR);
+        paint.setFilterBitmap(false);
+        canvas.drawPaint(paint);
+        ColorUtils.verifyColor("color should be a blue/red mix", Color.valueOf(0.5f, 0.0f, 0.5f),
+                dstBitmap.getColor(0, 0), 0.05f);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index 13a549d..8e3ebdd 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -19,6 +19,8 @@
 import static android.system.OsConstants.EINVAL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
@@ -44,7 +46,6 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -56,19 +57,25 @@
         System.loadLibrary("ctsgraphics_jni");
     }
 
-    private static String TAG = "FrameRateCtsActivity";
+    private static final String TAG = "FrameRateCtsActivity";
     private static final long FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS = 2;
     private static final long STABLE_FRAME_RATE_WAIT_SECONDS = 1;
     private static final long POST_BUFFER_INTERVAL_MILLIS = 500;
     private static final int PRECONDITION_WAIT_MAX_ATTEMPTS = 5;
     private static final long PRECONDITION_WAIT_TIMEOUT_SECONDS = 20;
     private static final long PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS = 3;
-    private static final float FRAME_RATE_TOLERANCE = 0.01f;
+    private static final float FRAME_RATE_TOLERANCE_STRICT = 0.01f;
+
+    // Tolerance which doesn't differentiate between the fractional refresh rate pairs, e.g.
+    // 59.94 and 60 will be considered the same refresh rate.
+    // Use this tolerance to verify the refresh rate after calling setFrameRate with
+    // {@Surface.FRAME_RATE_COMPATIBILITY_DEFAULT}.
+    private static final float FRAME_RATE_TOLERANCE_RELAXED = 0.1f;
 
     private DisplayManager mDisplayManager;
     private SurfaceView mSurfaceView;
     private Handler mHandler = new Handler(Looper.getMainLooper());
-    private Object mLock = new Object();
+    private final Object mLock = new Object();
     private Surface mSurface = null;
     private float mDeviceFrameRate;
     private ModeChangedEvents mModeChangedEvents = new ModeChangedEvents();
@@ -193,23 +200,20 @@
             mColor = color;
 
             if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
-                assertTrue("No parent surface", parentSurfaceControl != null);
+                assertNotNull("No parent surface", parentSurfaceControl);
                 mSurfaceControl = new SurfaceControl.Builder()
                                           .setParent(parentSurfaceControl)
                                           .setName(mName)
                                           .setBufferSize(destFrame.right - destFrame.left,
                                                   destFrame.bottom - destFrame.top)
                                           .build();
-                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-                try {
+                try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                     transaction.setGeometry(mSurfaceControl, null, destFrame, Surface.ROTATION_0)
                             .apply();
-                } finally {
-                    transaction.close();
                 }
                 mSurface = new Surface(mSurfaceControl);
             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
-                assertTrue("No parent surface", parentSurface != null);
+                assertNotNull("No parent surface", parentSurface);
                 mNativeSurfaceControl = nativeSurfaceControlCreate(parentSurface, mName,
                         destFrame.left, destFrame.top, destFrame.right, destFrame.bottom);
                 assertTrue("Failed to create a native SurfaceControl", mNativeSurfaceControl != 0);
@@ -231,14 +235,11 @@
                 rc = nativeWindowSetFrameRate(mSurface, frameRate, compatibility,
                         changeFrameRateStrategy);
             } else if (mApi == Api.SURFACE_CONTROL) {
-                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-                try {
+                try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                     transaction
-                        .setFrameRate(mSurfaceControl, frameRate, compatibility,
-                                changeFrameRateStrategy)
-                        .apply();
-                } finally {
-                    transaction.close();
+                            .setFrameRate(mSurfaceControl, frameRate, compatibility,
+                                    changeFrameRateStrategy)
+                            .apply();
                 }
             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
                 nativeSurfaceControlSetFrameRate(mNativeSurfaceControl, frameRate, compatibility,
@@ -262,9 +263,8 @@
             } else {
                 int rc = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
                 if (mApi == Api.ANATIVE_WINDOW) {
-                    assertTrue("Expected -EINVAL return value from invalid call to"
-                                    + " ANativeWindow_setFrameRate()",
-                            rc == -EINVAL);
+                    assertEquals("Expected -EINVAL return value from invalid call to"
+                            + " ANativeWindow_setFrameRate()", rc, -EINVAL);
                 }
             }
         }
@@ -274,11 +274,8 @@
                     String.format("Setting visibility for %s: %s", mName,
                             visible ? "visible" : "hidden"));
             if (mApi == Api.SURFACE || mApi == Api.ANATIVE_WINDOW || mApi == Api.SURFACE_CONTROL) {
-                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-                try {
+                try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                     transaction.setVisibility(mSurfaceControl, visible).apply();
-                } finally {
-                    transaction.close();
                 }
             } else if (mApi == Api.NATIVE_SURFACE_CONTROL) {
                 nativeSurfaceControlSetVisibility(mNativeSurfaceControl, visible);
@@ -309,11 +306,8 @@
                 mSurface = null;
             }
             if (mSurfaceControl != null) {
-                SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-                try {
+                try (SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                     transaction.reparent(mSurfaceControl, null).apply();
-                } finally {
-                    transaction.close();
                 }
                 mSurfaceControl.release();
                 mSurfaceControl = null;
@@ -407,7 +401,7 @@
         for (float frameRate : frameRates) {
             if (uniqueFrameRates.isEmpty()
                     || frameRate - uniqueFrameRates.get(uniqueFrameRates.size() - 1)
-                            >= FRAME_RATE_TOLERANCE) {
+                            >= FRAME_RATE_TOLERANCE_STRICT) {
                 uniqueFrameRates.add(frameRate);
             }
         }
@@ -430,12 +424,12 @@
                 && mode1.getPhysicalWidth() == mode2.getPhysicalWidth();
     }
 
-    private boolean isFrameRateMultiple(float higherFrameRate, float lowerFrameRate) {
+    private boolean isFrameRateMultiple(
+            float higherFrameRate, float lowerFrameRate, float tolerance) {
         float multiple = higherFrameRate / lowerFrameRate;
         int roundedMultiple = Math.round(multiple);
         return roundedMultiple > 0
-                && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate)
-                    <= FRAME_RATE_TOLERANCE;
+                && Math.abs(roundedMultiple * lowerFrameRate - higherFrameRate) <= tolerance;
     }
 
     // Returns two device-supported frame rates that aren't multiples of each other, or null if no
@@ -446,7 +440,8 @@
         for (int i = 0; i < frameRates.size(); i++) {
             for (int j = i + 1; j < frameRates.size(); j++) {
                 if (!isFrameRateMultiple(Math.max(frameRates.get(i), frameRates.get(j)),
-                            Math.min(frameRates.get(i), frameRates.get(j)))) {
+                            Math.min(frameRates.get(i), frameRates.get(j)),
+                            FRAME_RATE_TOLERANCE_RELAXED)) {
                     return new float[] {frameRates.get(i), frameRates.get(j)};
                 }
             }
@@ -456,8 +451,8 @@
 
     // Waits until our SurfaceHolder has a surface and the activity is resumed.
     private void waitForPreconditions() throws InterruptedException {
-        assertTrue(
-                "Activity was unexpectedly destroyed", mActivityState != ActivityState.DESTROYED);
+        assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+                ActivityState.DESTROYED);
         if (mSurface == null || mActivityState != ActivityState.RUNNING) {
             Log.i(TAG,
                     String.format(
@@ -473,8 +468,8 @@
                                mSurface != null, mActivityState == ActivityState.RUNNING),
                     timeRemainingMillis > 0);
             mLock.wait(timeRemainingMillis);
-            assertTrue("Activity was unexpectedly destroyed",
-                    mActivityState != ActivityState.DESTROYED);
+            assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+                    ActivityState.DESTROYED);
             nowNanos = System.nanoTime();
         }
         // Make sure any previous mode changes are completed.
@@ -483,8 +478,8 @@
 
     // Returns true if we encounter a precondition violation, false otherwise.
     private boolean waitForPreconditionViolation() throws InterruptedException {
-        assertTrue(
-                "Activity was unexpectedly destroyed", mActivityState != ActivityState.DESTROYED);
+        assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+                ActivityState.DESTROYED);
         long nowNanos = System.nanoTime();
         long endTimeNanos = nowNanos + PRECONDITION_VIOLATION_WAIT_TIMEOUT_SECONDS * 1_000_000_000L;
         while (mSurface != null && mActivityState == ActivityState.RUNNING) {
@@ -493,8 +488,8 @@
                 break;
             }
             mLock.wait(timeRemainingMillis);
-            assertTrue("Activity was unexpectedly destroyed",
-                    mActivityState != ActivityState.DESTROYED);
+            assertNotSame("Activity was unexpectedly destroyed", mActivityState,
+                    ActivityState.DESTROYED);
             nowNanos = System.nanoTime();
         }
         return mSurface == null || mActivityState != ActivityState.RUNNING;
@@ -507,7 +502,7 @@
     }
 
     // Returns true if we reached waitUntilNanos, false if some other event occurred.
-    private boolean waitForEvents(long waitUntilNanos, List<TestSurface> surfaces)
+    private boolean waitForEvents(long waitUntilNanos, TestSurface[] surfaces)
             throws InterruptedException {
         int numModeChangedEvents = mModeChangedEvents.size();
         long nowNanos = System.nanoTime();
@@ -539,21 +534,21 @@
         return true;
     }
 
-    private void waitForStableFrameRate() throws InterruptedException {
-        verifyCompatibleAndStableFrameRate(0, new ArrayList<>());
+    private void waitForStableFrameRate(TestSurface... surfaces) throws InterruptedException {
+        verifyCompatibleAndStableFrameRate(0, FRAME_RATE_TOLERANCE_STRICT, surfaces);
     }
 
     // Set expectedFrameRate to 0.0 to verify only stable frame rate.
-    private void verifyCompatibleAndStableFrameRate(float expectedFrameRate,
-            List<TestSurface> surfaces) throws InterruptedException {
+    private void verifyCompatibleAndStableFrameRate(float expectedFrameRate, float tolerance,
+            TestSurface... surfaces) throws InterruptedException {
         Log.i(TAG, "Verifying compatible and stable frame rate");
         long nowNanos = System.nanoTime();
         long gracePeriodEndTimeNanos =
                 nowNanos + FRAME_RATE_SWITCH_GRACE_PERIOD_SECONDS * 1_000_000_000L;
         while (true) {
-            if (expectedFrameRate > FRAME_RATE_TOLERANCE) { // expectedFrameRate > 0
+            if (expectedFrameRate > tolerance) { // expectedFrameRate > 0
                 // Wait until we switch to a compatible frame rate.
-                while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate)
+                while (!isFrameRateMultiple(mDeviceFrameRate, expectedFrameRate, tolerance)
                         && !waitForEvents(gracePeriodEndTimeNanos, surfaces)) {
                     // Empty
                 }
@@ -698,7 +693,8 @@
                     int initialNumEvents = mModeChangedEvents.size();
                     surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                             Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
-                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                    verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_RELAXED,
+                            surface);
                     verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
                             mModeChangedEvents.size());
@@ -707,7 +703,7 @@
                 surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                         Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
                 // Wait for potential mode switches
-                verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+                waitForStableFrameRate(surface);
                 currentMode = display.getMode();
 
                 // Seamed rates should never generate a seamed switch.
@@ -727,7 +723,8 @@
                     int initialNumEvents = mModeChangedEvents.size();
                     surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                             Surface.CHANGE_FRAME_RATE_ALWAYS);
-                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                    verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_RELAXED,
+                            surface);
                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
                             mModeChangedEvents.size());
                 }
@@ -781,14 +778,11 @@
             surfaceB.setFrameRate(frameRateB, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                     changeFrameRateStrategy);
 
-            ArrayList<TestSurface> surfaces = new ArrayList<>();
-            surfaces.add(surfaceA);
-            surfaces.add(surfaceB);
-
             if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
                 verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
             } else {
-                verifyCompatibleAndStableFrameRate(frameRateA, surfaces);
+                verifyCompatibleAndStableFrameRate(frameRateA, FRAME_RATE_TOLERANCE_STRICT,
+                        surfaceA, surfaceB);
             }
 
             verifyModeSwitchesDontChangeResolution(initialNumEvents,
@@ -800,7 +794,8 @@
             if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
                 verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
             } else {
-                verifyCompatibleAndStableFrameRate(frameRateB, surfaces);
+                verifyCompatibleAndStableFrameRate(frameRateB, FRAME_RATE_TOLERANCE_STRICT,
+                        surfaceA, surfaceB);
             }
             verifyModeSwitchesDontChangeResolution(initialNumEvents,
                     mModeChangedEvents.size());
@@ -850,7 +845,7 @@
     }
 
     public void testInvalidParams() throws InterruptedException {
-        runTestsWithPreconditions(api -> testInvalidParams(api), "invalid params behavior");
+        runTestsWithPreconditions(this::testInvalidParams, "invalid params behavior");
     }
 
     private void runOneSurfaceTest(Api api, OneSurfaceTestInterface test)
@@ -861,9 +856,6 @@
                     "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
                     /*visible=*/true, Color.RED);
 
-            ArrayList<TestSurface> surfaces = new ArrayList<>();
-            surfaces.add(surface);
-
             test.run(surface);
         } finally {
             if (surface != null) {
@@ -872,24 +864,6 @@
         }
     }
 
-    private void testSwitching(TestSurface surface, List<Float> frameRates, boolean expectSwitch,
-            int changeFrameRateStrategy) throws InterruptedException {
-        for (float frameRate : frameRates) {
-            int initialNumEvents = mModeChangedEvents.size();
-            surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
-                    changeFrameRateStrategy);
-
-            if (expectSwitch) {
-                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
-            }
-            if (changeFrameRateStrategy == Surface.CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS) {
-                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
-            }
-            verifyModeSwitchesDontChangeResolution(initialNumEvents,
-                    mModeChangedEvents.size());
-        }
-    }
-
     private void testMatchContentFramerate_None(Api api) throws InterruptedException {
         runOneSurfaceTest(api, (TestSurface surface) -> {
             Display display = getDisplay();
@@ -898,18 +872,18 @@
 
             for (float frameRate : frameRates) {
                 int initialNumEvents = mModeChangedEvents.size();
-                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                         Surface.CHANGE_FRAME_RATE_ALWAYS);
 
-                assertTrue("Mode switches are not expected but these were detected "
-                        + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
-                        mModeChangedEvents.size() == initialNumEvents);
+                assertEquals("Mode switches are not expected but these were detected "
+                                + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
+                        mModeChangedEvents.size(), initialNumEvents);
             }
         });
     }
 
     public void testMatchContentFramerate_None() throws InterruptedException {
-        runTestsWithPreconditions(api -> testMatchContentFramerate_None(api),
+        runTestsWithPreconditions(this::testMatchContentFramerate_None,
                 "testMatchContentFramerate_None");
     }
 
@@ -922,27 +896,27 @@
 
             for (float frameRate : frameRatesToTest) {
                 int initialNumEvents = mModeChangedEvents.size();
-                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                         Surface.CHANGE_FRAME_RATE_ALWAYS);
 
-                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_STRICT, surface);
                 verifyModeSwitchesDontChangeResolution(initialNumEvents,
                         mModeChangedEvents.size());
             }
 
             // Reset to default
-            surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+            surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                     Surface.CHANGE_FRAME_RATE_ALWAYS);
 
             // Wait for potential mode switches.
-            verifyCompatibleAndStableFrameRate(0, Arrays.asList(surface));
+            waitForStableFrameRate(surface);
 
             currentMode = display.getMode();
             List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
 
             for (float frameRate : seamedRefreshRates) {
                 int initialNumEvents = mModeChangedEvents.size();
-                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                         Surface.CHANGE_FRAME_RATE_ALWAYS);
 
                 // Mode switches may have occurred, make sure they were all seamless.
@@ -954,7 +928,7 @@
     }
 
     public void testMatchContentFramerate_Auto() throws InterruptedException {
-        runTestsWithPreconditions(api -> testMatchContentFramerate_Auto(api),
+        runTestsWithPreconditions(this::testMatchContentFramerate_Auto,
                 "testMatchContentFramerate_Auto");
     }
 
@@ -964,10 +938,10 @@
             List<Float> frameRates = getRefreshRates(display.getMode(), display);
             for (float frameRate : frameRates) {
                 int initialNumEvents = mModeChangedEvents.size();
-                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
                         Surface.CHANGE_FRAME_RATE_ALWAYS);
 
-                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyCompatibleAndStableFrameRate(frameRate, FRAME_RATE_TOLERANCE_STRICT, surface);
                 verifyModeSwitchesDontChangeResolution(initialNumEvents,
                         mModeChangedEvents.size());
             }
@@ -975,7 +949,7 @@
     }
 
     public void testMatchContentFramerate_Always() throws InterruptedException {
-        runTestsWithPreconditions(api -> testMatchContentFramerate_Always(api),
+        runTestsWithPreconditions(this::testMatchContentFramerate_Always,
                 "testMatchContentFramerate_Always");
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.java b/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.java
new file mode 100644
index 0000000..7ec4fa9
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/HardwareRendererTest.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 android.graphics.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.HardwareRenderer;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class HardwareRendererTest {
+
+    @Test
+    public void isDrawingEnabled_defaultsTrue() {
+        assertThat(HardwareRenderer.isDrawingEnabled()).isTrue();
+    }
+
+    @Test
+    public void setDrawingEnabled() {
+        HardwareRenderer.setDrawingEnabled(false);
+
+        assertThat(HardwareRenderer.isDrawingEnabled()).isFalse();
+
+        HardwareRenderer.setDrawingEnabled(true);
+        assertThat(HardwareRenderer.isDrawingEnabled()).isTrue();
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
index a4915b7..f829e80 100644
--- a/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/NinePatchTest.java
@@ -86,6 +86,16 @@
         mNinePatch = new NinePatch(mBitmap, mChunk, NAME);
         assertEquals(mBitmap, mNinePatch.getBitmap());
         assertEquals(NAME, mNinePatch.getName());
+
+        boolean caughtException = false;
+        try {
+            mNinePatch = new NinePatch(mBitmap, null);
+        } catch (Exception e) {
+            // No need to catch it, just asserting that it was thrown
+            caughtException = true;
+        } finally {
+            assertTrue(caughtException);
+        }
     }
 
     @Test
diff --git a/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java b/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
index d53cec7..7fb066a 100644
--- a/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/OutlineTest.java
@@ -162,17 +162,17 @@
         Rect outRect = new Rect();
         outline.setOval(0, 0, 50, 51); // different x & y radii, so not round rect
         assertFalse(outline.getRect(outRect)); // not round rect, doesn't work
-        assertFalse(outline.canClip()); // not round rect, doesn't work
+        assertTrue(outline.canClip());
         assertFalse(outline.isEmpty());
 
         outline.setOval(0, 0, 50, 50); // same x & y radii, so round rect
         assertTrue(outline.getRect(outRect)); // is round rect, so works
-        assertTrue(outline.canClip()); // is round rect, so works
+        assertTrue(outline.canClip());
         assertFalse(outline.isEmpty());
 
         outline.setOval(new Rect(0, 0, 50, 50)); // same x & y radii, so round rect
         assertTrue(outline.getRect(outRect)); // is round rect, so works
-        assertTrue(outline.canClip()); // is round rect, so works
+        assertTrue(outline.canClip());
         assertFalse(outline.isEmpty());
     }
 
@@ -187,6 +187,8 @@
 
         path.addCircle(50, 50, 50, Path.Direction.CW);
         outline.setPath(path);
+        assertTrue(outline.canClip());
+
         assertFalse(outline.isEmpty());
     }
 
@@ -239,5 +241,10 @@
         outline.offset(-5, -10);
         assertTrue(outline.getRect(outRect));
         assertEquals(new Rect(0, 0, 10, 10), outRect);
+
+        // Test cumulative effects
+        outline.offset(-5, -10);
+        assertTrue(outline.getRect(outRect));
+        assertEquals(new Rect(-5, -10, 5, 0), outRect);
     }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
index 1b41d09..379a732 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SetFrameRateTest.java
@@ -20,16 +20,16 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.app.UiAutomation;
+import android.Manifest;
 import android.content.Context;
-import android.util.Log;
+import android.hardware.display.DisplayManager;
 import android.view.Surface;
-import android.view.SurfaceControl;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.compatibility.common.util.DisplayUtil;
 
 import org.junit.After;
@@ -46,38 +46,39 @@
     @Rule
     public ActivityTestRule<FrameRateCtsActivity> mActivityRule =
             new ActivityTestRule<>(FrameRateCtsActivity.class);
-    private long mFrameRateFlexibilityToken;
+
+    @Rule
+    public final AdoptShellPermissionsRule mShellPermissionsRule =
+            new AdoptShellPermissionsRule(getInstrumentation().getUiAutomation(),
+                    Manifest.permission.HDMI_CEC,
+                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+                    Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
+    private DisplayManager mDisplayManager;
+    private int mInitialRefreshRateSwitchingType;
 
     @Before
     public void setUp() throws Exception {
-        // Surface flinger requires the ACCESS_SURFACE_FLINGER permission to acquire a frame
-        // rate flexibility token. Switch to shell permission identity so we'll have the
-        // necessary permission when surface flinger checks.
-        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
-        uiAutomation.adoptShellPermissionIdentity();
-
         Context context = getInstrumentation().getTargetContext();
         assertTrue("Physical display is expected.", DisplayUtil.isDisplayConnected(context));
 
-        try {
-            // Take ownership of the frame rate flexibility token, if we were able
-            // to get one - we'll release it in tearDown().
-            mFrameRateFlexibilityToken = SurfaceControl.acquireFrameRateFlexibilityToken();
-        } finally {
-            uiAutomation.dropShellPermissionIdentity();
-        }
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
 
-        if (mFrameRateFlexibilityToken == 0) {
-            Log.e(TAG, "Failed to acquire frame rate flexibility token."
-                    + " SetFrameRate tests may fail.");
-        }
+        // Prevent DisplayManager from limiting the allowed refresh rate range based on
+        // non-app policies (e.g. low battery, user settings, etc).
+        mDisplayManager = activity.getSystemService(DisplayManager.class);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+
+        mInitialRefreshRateSwitchingType = DisplayUtil.getRefreshRateSwitchingType(mDisplayManager);
+        mDisplayManager.setRefreshRateSwitchingType(
+                DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
     }
 
     @After
     public void tearDown() {
-        if (mFrameRateFlexibilityToken != 0) {
-            SurfaceControl.releaseFrameRateFlexibilityToken(mFrameRateFlexibilityToken);
-            mFrameRateFlexibilityToken = 0;
+        if (mDisplayManager != null) {
+            mDisplayManager.setRefreshRateSwitchingType(mInitialRefreshRateSwitchingType);
+            mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
         }
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
index e5a3ed7..bf17876 100644
--- a/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/SystemPaletteTest.java
@@ -18,36 +18,40 @@
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
 import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.R;
 import android.content.Context;
 import android.graphics.Color;
-import android.graphics.cts.utils.Cam;
+import android.provider.Settings;
+import android.util.Log;
 import android.util.Pair;
 
-
 import androidx.annotation.ColorInt;
 import androidx.core.graphics.ColorUtils;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.PollingCheck;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
 
-import java.util.ArrayList;
+import java.io.IOException;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 
-
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class SystemPaletteTest {
 
-    // Hue goes from 0 to 360
-    private static final int MAX_HUE_DISTANCE = 15;
+    private static final boolean DEBUG = false;
+    private static final String TAG = "SystemPaletteTest";
 
     @Test
     public void testShades0and1000() {
@@ -70,78 +74,67 @@
     }
 
     @Test
-    public void testAllColorsBelongToSameFamily() {
+    public void testThemeStyles() {
         final Context context = getInstrumentation().getTargetContext();
-        List<int[]> allPalettes = Arrays.asList(getAllAccent1Colors(context),
-                getAllAccent2Colors(context), getAllAccent3Colors(context),
-                getAllNeutral1Colors(context), getAllNeutral2Colors(context));
+        forEachThemeDefinition((color, style, expectedPalette) -> {
+            // Update setting, so system colors will change
+            runWithShellPermissionIdentity(() -> {
+                Settings.Secure.putString(context.getContentResolver(),
+                        Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES,
+                        "{\"android.theme.customization.system_palette\":\"" + color
+                                + "\",\"android.theme.customization.theme_style\":\"" + style
+                                + "\"}");
+            });
 
-        final float[] tones = {100, 99, 95, 90, 80, 70, 60, 49, 40, 30, 20, 10, 0};
-        for (int[] palette : allPalettes) {
-            // Determine the median hue of the palette. Each color in the palette colors will have
-            // its hue measured against the median hue. If the difference is too large, the test
-            // fails.
-            List<Float> hues = new ArrayList<>();
-            for (int i = 0; i < palette.length - 1; i++) {
-                // Avoid measuring hue of colors above 90 or below 10 in tone.
-                //
-                // Converting from HCT to sRGB from display quantizes colors - i.e. not every
-                // HCT color can be expressed in sRGB. As colors approach the extreme tones, white
-                // at 100 and black at 0, hues begin overlapping overlay - made up example: hues
-                // 110 to 128 at tone 95, when mapped to sRGB for display, all end up being measured
-                // as hue 114.
-                final float tone = tones[i];
-                if (tone <= 10.0 || tone > 90.0) {
-                    continue;
-                }
-                final Cam cam = Cam.fromInt(palette[i]);
-                hues.add(cam.getHue());
-            }
-            Collections.sort(hues);
-            final float medianHue = hues.get(hues.size() / 2);
+            final int[] allColors = new int[65];
+            new PollingCheck(5_000L, "Invalid tonal palettes for " + color + " " + style) {
+                @Override
+                protected boolean check() {
 
-            // Measure the hue of each color in the palette against the median hue.
-            for (int i = 0; i < palette.length - 1; i++) {
-                final float tone = tones[i];
-                // Skip testing hue of extreme tones, due to overlap due to quantization that occurs
-                // when converting from HCT to sRGB for display.
-                if (tone <= 10.0 || tone > 90.0) {
-                    continue;
+                    System.arraycopy(getAllAccent1Colors(context), 0, allColors, 0, 13);
+                    System.arraycopy(getAllAccent2Colors(context), 0, allColors, 13, 13);
+                    System.arraycopy(getAllAccent3Colors(context), 0, allColors, 26, 13);
+                    System.arraycopy(getAllNeutral1Colors(context), 0, allColors, 39, 13);
+                    System.arraycopy(getAllNeutral2Colors(context), 0, allColors, 52, 13);
+
+                    if (DEBUG) {
+                        Log.d(TAG, "Expected:\n" + Arrays.toString(expectedPalette)
+                                        + "\nActual:\n" + Arrays.toString(allColors));
+                    }
+
+                    return Arrays.equals(allColors, expectedPalette);
                 }
-                final Cam cam = Cam.fromInt(palette[i]);
-                final float hue = cam.getHue();
-                final boolean hueWithinTolerance = deltaHueWithinTolerance(hue, medianHue);
-                assertWithMessage("Color " + toHctString(cam)
-                        + " has different hue compared to median hue " + Math.round(medianHue)
-                        + " of palette: " + Arrays.toString(palette))
-                        .that(hueWithinTolerance).isTrue();
+            }.run();
+        });
+    }
+
+    private void forEachThemeDefinition(ThemeEvaluator evaluator) {
+        final Context context = getInstrumentation().getTargetContext();
+        final XmlPullParser parser = context.getResources()
+                .getXml(android.graphics.cts.R.xml.valid_themes);
+        try {
+            parser.next();
+            parser.next();
+            parser.require(XmlPullParser.START_TAG, null, "themes");
+            while (parser.next() != XmlPullParser.END_TAG) {
+                parser.require(XmlPullParser.START_TAG, null, "theme");
+                final String color = parser.getAttributeValue(null, "color");
+                while (parser.next() != XmlPullParser.END_TAG) {
+                    String styleName = parser.getName();
+                    parser.next();
+                    int[] colors = Arrays.stream(parser.getText().split(","))
+                            .mapToInt(s -> Color.parseColor("#" + s))
+                            .toArray();
+                    parser.next();
+                    parser.require(XmlPullParser.END_TAG, null, styleName);
+                    evaluator.apply(color, styleName.toUpperCase(), colors);
+                }
             }
+        } catch (XmlPullParserException | IOException e) {
+            throw new RuntimeException("Error parsing xml", e);
         }
     }
 
-    private static String toHctString(Cam cam) {
-        final double[] labColor = new double[3];
-        ColorUtils.colorToLAB(cam.viewedInSrgb(), labColor);
-        return "H" + Math.round(cam.getHue()) + " C" + Math.round(cam.getChroma()) + " T"
-                + Math.round(labColor[0]);
-    }
-
-    /**
-     * Compare if color A and B have similar hue, in gCAM space.
-     *
-     * @param colorA Color 1
-     * @param colorB Color 2
-     * @return True when colors have similar hue.
-     */
-    private boolean deltaHueWithinTolerance(float hueA, float hueB) {
-
-        float hue1 = Math.max(hueA, hueB);
-        float hue2 = Math.min(hueA, hueB);
-
-        float diffDegrees = 180.0f - Math.abs(Math.abs(hue1 - hue2) - 180.0f);
-        return diffDegrees < MAX_HUE_DISTANCE;
-    }
-
     @Test
     public void testColorsMatchExpectedLuminance() {
         final Context context = getInstrumentation().getTargetContext();
@@ -318,4 +311,8 @@
         colors[12] = context.getColor(R.color.system_neutral2_1000);
         return colors;
     }
+
+    private interface ThemeEvaluator {
+        void apply(String color, String style, int[] expectedPalette);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
index a0ba34b..fce9b34 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AdaptiveIconDrawableTest.java
@@ -55,6 +55,7 @@
     @Test
     public void testConstructor() {
         new AdaptiveIconDrawable(null, null);
+        new AdaptiveIconDrawable(null, null, null);
     }
 
     @Test
@@ -142,6 +143,21 @@
         assertEquals(128, iconDrawable.getAlpha());
     }
 
+    @Test
+    public void testGetMonochrome() {
+        ColorDrawable drawable = new ColorDrawable(Color.RED);
+        AdaptiveIconDrawable iconDrawable = new AdaptiveIconDrawable(null, null, drawable);
+        assertEquals(drawable, iconDrawable.getMonochrome());
+    }
+
+    @Test
+    public void testMonochromeInflated() {
+        Resources r = InstrumentationRegistry.getTargetContext().getResources();
+        AdaptiveIconDrawable iconDrawable = (AdaptiveIconDrawable) r.getDrawable(
+                R.drawable.adaptiveicondrawable);
+        assertNotNull(iconDrawable.getMonochrome());
+    }
+
     /**
      * When setBound isn't called before draw method is called.
      * Nothing is drawn.
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
index da2eb16..92c8428 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimatedImageDrawableTest.java
@@ -48,8 +48,8 @@
 import androidx.test.rule.ActivityTestRule;
 
 import com.android.compatibility.common.util.BitmapUtils;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -92,7 +92,7 @@
 
     private void setupActivity() {
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mImageView = mActivity.findViewById(R.id.animated_image);
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
index d2fbcf8..a8e6d75 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/GradientDrawableTest.java
@@ -715,6 +715,62 @@
     }
 
     @Test
+    public void testGradientColorInflationWithThemeAndNonThemeResources() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Theme theme = context.getResources().newTheme();
+        theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+        final Theme ctxTheme = context.getTheme();
+        ctxTheme.setTo(theme);
+
+        GradientDrawable drawable = (GradientDrawable)
+                ctxTheme.getDrawable(R.drawable.gradientdrawable_color_mix_theme);
+
+        int[] colors = drawable.getColors();
+        drawable.setColors(colors);
+        assertEquals(3, colors.length);
+        assertEquals(context.getColor(R.color.colorAccent), colors[0]);
+        assertEquals(context.getColor(R.color.colorPrimary), colors[1]);
+        assertEquals(context.getColor(R.color.colorPrimaryDark), colors[2]);
+    }
+
+    @Test
+    public void testGradientColorInflationWithAllThemeResources() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Theme theme = context.getResources().newTheme();
+        theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+        final Theme ctxTheme = context.getTheme();
+        ctxTheme.setTo(theme);
+
+        GradientDrawable drawable = (GradientDrawable)
+                ctxTheme.getDrawable(R.drawable.gradientdrawable_color_all_theme);
+
+        int[] colors = drawable.getColors();
+        drawable.setColors(colors);
+        assertEquals(3, colors.length);
+        assertEquals(context.getColor(R.color.colorPrimary), colors[0]);
+        assertEquals(context.getColor(R.color.colorPrimaryDark), colors[1]);
+        assertEquals(context.getColor(R.color.colorPrimaryDark), colors[2]);
+    }
+
+    @Test
+    public void testGradientColorNoCenterColorInflationWithThemeAndNonThemeResources() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final Theme theme = context.getResources().newTheme();
+        theme.applyStyle(R.style.Theme_MixedGradientTheme, true);
+        final Theme ctxTheme = context.getTheme();
+        ctxTheme.setTo(theme);
+
+        GradientDrawable drawable = (GradientDrawable)
+                ctxTheme.getDrawable(R.drawable.gradientdrawable_noCenterColor_all_theme);
+
+        int[] colors = drawable.getColors();
+        drawable.setColors(colors);
+        assertEquals(2, colors.length);
+        assertEquals(context.getColor(R.color.colorPrimary), colors[0]);
+        assertEquals(context.getColor(R.color.colorPrimaryDark), colors[1]);
+    }
+
+    @Test
     public void testRadialInflationWithThemeAndNonThemeResources() {
         final Context context = new ContextThemeWrapper(InstrumentationRegistry.getTargetContext(),
                 R.style.Theme_MixedGradientTheme);
diff --git a/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
index 0e2711d..7725e9f 100644
--- a/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
+++ b/tests/tests/graphics/src/android/graphics/fonts/FontManagerTest.java
@@ -20,6 +20,7 @@
 import static android.graphics.fonts.FontStyle.FONT_WEIGHT_NORMAL;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.fail;
 
@@ -271,5 +272,27 @@
         }
     }
 
+    @Test
+    public void fontManager_NotoColorEmojiAvailable() throws IOException {
+        FontConfig fontConfig = getFontConfig();
+        boolean hasNotoColorEmoji = false;
+
+        for (FontConfig.FontFamily family : fontConfig.getFontFamilies()) {
+
+            if (family.getLocaleList().size() == 1
+                    && "Zsye".equals(family.getLocaleList().get(0).getScript())) {
+                if ("NotoColorEmoji".equals(family.getFontList().get(0).getPostScriptName())) {
+                    hasNotoColorEmoji = true;
+                }
+            }
+        }
+
+        assertWithMessage("NotoColorEmoji must be included."
+                + "If you include your own font, place your emoji just before the "
+                + "NotoColorEmoji.ttf")
+            .that(hasNotoColorEmoji)
+                .isTrue();
+    }
+
     // TODO: Add more tests once we sign test fonts.
 }
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java b/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java
new file mode 100644
index 0000000..38ed6a5
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/text/cts/HyphenationTest.java
@@ -0,0 +1,432 @@
+/*
+ * 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.graphics.text.cts;
+
+import static android.graphics.text.LineBreaker.BREAK_STRATEGY_BALANCED;
+import static android.graphics.text.LineBreaker.HYPHENATION_FREQUENCY_FULL;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.text.LineBreaker;
+import android.graphics.text.MeasuredText;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Locale;
+
+/**
+ * Verify the hyphenation pattern works as expected.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class HyphenationTest {
+    private static Paint sPaint;
+
+    @BeforeClass
+    public static void classSetUp() {
+        sPaint = new Paint();
+        Context context = InstrumentationRegistry.getTargetContext();
+        AssetManager am = context.getAssets();
+        Typeface tf = new Typeface.Builder(am, "fonts/layout/linebreak.ttf").build();
+        sPaint.setTypeface(tf);
+    }
+
+    @Test
+    public void testAfrikaansPattern() {
+        final String locale = "af";
+        final String text = "zastavila zastavila zastavila zastavila";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | zastavila zas- |
+        // | tavila zasta-  |
+        // | vila zastavila |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testAlbanianPattern() {
+        final String locale = "sq";
+        final String text = "vazhduar vazhduar vazhduar vazhduar";
+        final float textSize = 15.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | vazhdu-     |
+        // | ar vazhdu-  |
+        // | ar vazhdu-  |
+        // | ar vazhduar |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(4, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(3));
+    }
+
+    @Test
+    public void testAmharicPattern() {
+        final String locale = "am";
+        final String text = "መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን መጋበዛቸውን";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | መጋበዛቸውን መጋበዛቸ-  |
+        // | ውን መጋበዛቸውን መ-  |
+        // | ጋበዛቸውን መጋበዛቸውን  |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testCzechPattern() {
+        final String locale = "cs";
+        final String text = "epidemiologická epidemiologická epidemiologická";
+        final float textSize = 15.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | epidemiolo-  |
+        // | gická epi-   |
+        // | demiolo-     |
+        // | gická epide- |
+        // | miologická   |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(5, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(3));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(4));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(4));
+    }
+
+    @Test
+    public void testDutchPattern() {
+        final String locale = "nl";
+        final String text = "beschuldigt beschuldigt beschuldigt beschuldigt";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | beschuldigt be-    |
+        // | schuldigt beschul- |
+        // | digt beschuldigt   |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testEnglishPattern() {
+        final String locale = "en";
+        final String text = "hyphenation hyphenation hyphenation hyphenation";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | hyphenation hy-   |
+        // | phenation hyphen- |
+        // | ation hyphenation |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testGalicianPattern() {
+        final String locale = "gl";
+        final String text = "tecnoloxía tecnoloxía tecnoloxía";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | tecnoloxía tecno- |
+        // | loxía tecnoloxía  |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(2, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(1));
+    }
+
+    @Test
+    public void testGeorgianPattern() {
+        final String locale = "ka";
+        final String text = "ინფორმაციით ინფორმაციით ინფორმაციით ინფორმაციით";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | ინფორმაცი-     |
+        // | ით ინფორმაცი-  |
+        // | ით ინფორმაცი-  |
+        // | ით ინფორმაციით |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testGreekPattern() {
+        final String locale = "el";
+        final String text = "εργαζόμενους εργαζόμενους εργαζόμενους";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | εργαζόμενους ερ- |
+        // | γαζόμενους ερ-   |
+        // | γαζόμενους       |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testItalianPattern() {
+        final String locale = "it";
+        final String text = "Assicurati Assicurati Assicurati Assicurati";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | Assicurati Assi- |
+        // | curati Assicu-   |
+        // | rati Assicurati  |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testLatvianPattern() {
+        final String locale = "lv";
+        final String text = "verifikāciju verifikāciju verifikāciju verifikāciju";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | verifikāciju veri- |
+        // | fikāciju verifikā- |
+        // | ciju verifikāciju  |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testLithuanianPattern() {
+        final String locale = "lt";
+        final String text = "Pasirūpinkite Pasirūpinkite Pasirūpinkite Pasirūpinkite";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | Pasirūpinki-     |
+        // | te Pasirūpinki-  |
+        // | te Pasirūpinki-  |
+        // | te Pasirūpinkite |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(4, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(3));
+    }
+
+    @Test
+    public void testSlovakPattern() {
+        final String locale = "sk";
+        final String text = "epidemiologická epidemiologická epidemiologická";
+        final float textSize = 15.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | epidemiolo-  |
+        // | gická epi-   |
+        // | demiolo-     |
+        // | gická epide- |
+        // | miologická   |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(5, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(3));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(3));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(4));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(4));
+    }
+
+    @Test
+    public void testSwedishPattern() {
+        final String locale = "sv";
+        final String text = "nederbörd nederbörd nederbörd nederbörd";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | nederbörd ne-  |
+        // | derbörd neder- |
+        // | börd nederbörd |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    @Test
+    public void testUkrainianPattern() {
+        final String locale = "uk";
+        final String text = "Увімкніть Увімкніть Увімкніть Увімкніть";
+        final float textSize = 10.0f;
+        sPaint.setTextLocale(new Locale(locale));
+        sPaint.setTextSize(textSize);
+
+        // The visual BALANCED line break output is like
+        // | Увімкніть Уві-   |
+        // | мкніть Уві-      |
+        // | мкніть Увімкніть |
+        final LineBreaker.Result r = computeLineBreaks(text);
+
+        assertEquals(3, r.getLineCount());
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+    }
+
+    private LineBreaker.Result computeLineBreaks(String text) {
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+                .build();
+        final LineBreaker.ParagraphConstraints c = new LineBreaker.ParagraphConstraints();
+        c.setWidth(180f);
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_NORMAL)
+                .appendStyleRun(sPaint, text.length(), false)
+                .build();
+        return lb.computeLineBreaks(mt, c, 0);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
index 5a20570..c02f052 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/LineBreakerTest.java
@@ -584,6 +584,80 @@
     }
 
     @Test
+    public void testLineBreak_Balanced_Hyphenation() {
+        // The visual BALANCED line break output is like
+        // |hyphenation hy-   |
+        // |phenation hyphen- |
+        // |ation hyphenation |
+        final String text = "hyphenation hyphenation hyphenation hyphenation";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(180f);
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_NORMAL)
+                .appendStyleRun(sPaint, text.length(), false)
+                .build();
+        final Result r = lb.computeLineBreaks(mt, c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(14, r.getLineBreakOffset(0));
+        assertEquals(30, r.getLineBreakOffset(1));
+        assertEquals(47, r.getLineBreakOffset(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(150.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(170.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(170.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
+    public void testLineBreak_Balanced_Hyphenation_IgnoreKerning() {
+        // The visual BALANCED line break output is like
+        // |hyphenation hy-   |
+        // |phenation hyphen- |
+        // |ation hyphenation |
+        //
+        // Note: The line break result should be same to non-fast version.
+        final String text = "hyphenation hyphenation hyphenation hyphenation";
+        final LineBreaker lb = new LineBreaker.Builder()
+                .setBreakStrategy(BREAK_STRATEGY_BALANCED)
+                .setHyphenationFrequency(HYPHENATION_FREQUENCY_FULL)
+                .build();
+        final ParagraphConstraints c = new ParagraphConstraints();
+        c.setWidth(180f);
+        MeasuredText mt = new MeasuredText.Builder(text.toCharArray())
+                .setComputeHyphenation(MeasuredText.Builder.HYPHENATION_MODE_FAST)
+                .appendStyleRun(sPaint, text.length(), false)
+                .build();
+        final Result r = lb.computeLineBreaks(mt, c, 0);
+        assertEquals(3, r.getLineCount());
+        assertEquals(14, r.getLineBreakOffset(0));
+        assertEquals(30, r.getLineBreakOffset(1));
+        assertEquals(47, r.getLineBreakOffset(2));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(0));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(0));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(1));
+        assertEquals(Paint.END_HYPHEN_EDIT_INSERT_HYPHEN, r.getEndLineHyphenEdit(1));
+        assertEquals(Paint.START_HYPHEN_EDIT_NO_EDIT, r.getStartLineHyphenEdit(2));
+        assertEquals(Paint.END_HYPHEN_EDIT_NO_EDIT, r.getEndLineHyphenEdit(2));
+        assertFalse(r.hasLineTab(0));
+        assertFalse(r.hasLineTab(1));
+        assertFalse(r.hasLineTab(2));
+        assertEquals(150.0f, r.getLineWidth(0), 0.0f);
+        assertEquals(170.0f, r.getLineWidth(1), 0.0f);
+        assertEquals(170.0f, r.getLineWidth(2), 0.0f);
+    }
+
+    @Test
     public void testLineBreak_ZeroWidthTab() {
         final String text = "Hi, \tWorld.";
         final LineBreaker lb = new LineBreaker.Builder()
diff --git a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
index 52a2a74..085ef45 100644
--- a/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
+++ b/tests/tests/graphics/src/android/graphics/text/cts/MeasuredTextTest.java
@@ -28,6 +28,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
 import android.graphics.text.MeasuredText;
 import android.text.PrecomputedText;
 import android.text.SpannableStringBuilder;
@@ -61,6 +62,11 @@
         String text = "Hello, World";
         new MeasuredText.Builder(text.toCharArray())
                 .appendStyleRun(sPaint, text.length(), false /* isRtl */).build();
+
+        LineBreakConfig config = new LineBreakConfig();
+        config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+        new MeasuredText.Builder(text.toCharArray())
+                .appendStyleRun(sPaint, config, text.length(), false /* isRtl */).build();
     }
 
     @Test
diff --git a/tests/tests/hardware/Android.bp b/tests/tests/hardware/Android.bp
index 7617806..81f02a4 100644
--- a/tests/tests/hardware/Android.bp
+++ b/tests/tests/hardware/Android.bp
@@ -44,6 +44,9 @@
         "libctshardware_jni",
         "libnativehelper_compat_libc++",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
     sdk_version: "test_current",
 }
diff --git a/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json
new file mode 100644
index 0000000..1ab4894
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_keyeventtests.json
@@ -0,0 +1,51 @@
+[
+  {
+    "name": "Initial check - no events should be produced",
+    "reports": [
+      [0x01, 0x00],
+      [0x01, 0x00]
+    ],
+    "source": "KEYBOARD",
+    "events": [
+    ]
+  },
+
+  {
+    "name": "Press VOLUME_UP",
+    "reports": [
+      [0x01, 0x01],
+      [0x01, 0x00]
+    ],
+    "source": "KEYBOARD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_UP"},
+      {"action": "UP", "keycode": "VOLUME_UP"}
+    ]
+  },
+
+  {
+    "name": "Press VOLUME_DOWN",
+    "reports": [
+      [0x01, 0x02],
+      [0x01, 0x00]
+    ],
+    "source": "KEYBOARD",
+    "events": [
+      {"action": "DOWN", "keycode": "VOLUME_DOWN"},
+      {"action": "UP", "keycode": "VOLUME_DOWN"}
+    ]
+  },
+
+  {
+    "name": "Press play/pause (black round middle button)",
+    "reports": [
+      [0x01, 0x04],
+      [0x01, 0x00]
+    ],
+    "source": "KEYBOARD",
+    "events": [
+      {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
+      {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
+    ]
+  }
+]
diff --git a/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json
new file mode 100644
index 0000000..1b87f57
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_pixelusbcearbuds_register.json
@@ -0,0 +1,13 @@
+{
+  "id": 1,
+  "command": "register",
+  "name": "Google Pixel USB-C earbuds",
+  "vid": 0x18d1,
+  "pid": 0x5003,
+  "bus": "usb",
+  "source": "KEYBOARD",
+  "descriptor": [0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x01, 0x15, 0x00, 0x25, 0x01, 0x75,
+    0x01, 0x95, 0x02, 0x09, 0xe9, 0x09, 0xea, 0x81, 0x02, 0x95, 0x01, 0x09, 0xcd, 0x81, 0x02,
+    0x95, 0x05, 0x81, 0x01, 0x85, 0x04, 0x09, 0x00, 0x75, 0x08, 0x95, 0x26, 0x91, 0x02, 0x85,
+    0x05, 0x09, 0x00, 0x95, 0x22, 0x81, 0x02, 0xc0]
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java b/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java
new file mode 100644
index 0000000..f7430d6
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/DataSpaceTest.java
@@ -0,0 +1,260 @@
+/*
+ * 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.hardware.cts;
+
+import static android.opengl.GLES20.glDeleteTextures;
+import static android.opengl.GLES20.glGenTextures;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.hardware.DataSpace;
+import android.media.Image;
+import android.media.ImageReader;
+import android.media.ImageWriter;
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLSurface;
+import android.view.Surface;
+
+import androidx.test.annotation.UiThreadTest;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DataSpaceTest {
+    private SurfaceTexture mSurfaceTexture;
+    private Surface mSurface;
+    private int[] mTex;
+    private ImageWriter mWriter;
+    private ImageReader mReader;
+
+    private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY;
+    private EGLConfig mEglConfig = null;
+    private EGLSurface mEglSurface = EGL14.EGL_NO_SURFACE;
+    private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT;
+
+    @UiThreadTest
+    @Before
+    public void setUp() throws Throwable {
+        mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (mEglDisplay == EGL14.EGL_NO_DISPLAY) {
+            throw new RuntimeException("no EGL display");
+        }
+        int[] major = new int[1];
+        int[] minor = new int[1];
+        if (!EGL14.eglInitialize(mEglDisplay, major, 0, minor, 0)) {
+            throw new RuntimeException("error in eglInitialize");
+        }
+
+        // If we could rely on having EGL_KHR_surfaceless_context and EGL_KHR_context_no_config, we
+        // wouldn't have to create a config or pbuffer at all.
+
+        int[] numConfigs = new int[1];
+        EGLConfig[] configs = new EGLConfig[1];
+        if (!EGL14.eglChooseConfig(mEglDisplay, new int[] {
+                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_PBUFFER_BIT,
+                EGL14.EGL_NONE}, 0, configs, 0, 1, numConfigs, 0)) {
+            throw new RuntimeException("eglChooseConfig failed");
+        }
+        mEglConfig = configs[0];
+
+        mEglSurface = EGL14.eglCreatePbufferSurface(mEglDisplay, mEglConfig,
+            new int[] {EGL14.EGL_WIDTH, 1, EGL14.EGL_HEIGHT, 1, EGL14.EGL_NONE}, 0);
+        if (mEglSurface == EGL14.EGL_NO_SURFACE) {
+            throw new RuntimeException("eglCreatePbufferSurface failed");
+        }
+
+        mEglContext = EGL14.eglCreateContext(mEglDisplay, mEglConfig, EGL14.EGL_NO_CONTEXT,
+            new int[] {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}, 0);
+        if (mEglContext == EGL14.EGL_NO_CONTEXT) {
+            throw new RuntimeException("eglCreateContext failed");
+        }
+
+        if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+            throw new RuntimeException("eglMakeCurrent failed");
+        }
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        if (mReader != null) {
+            mReader.close();
+            mReader = null;
+        }
+        if (mWriter != null) {
+            mWriter.close();
+            mWriter = null;
+        }
+        if (mSurface != null) {
+            mSurface.release();
+            mSurface = null;
+        }
+        if (mSurfaceTexture != null) {
+            mSurfaceTexture.release();
+            mSurfaceTexture = null;
+            glDeleteTextures(1, mTex, 0);
+        }
+        if (mEglDisplay != EGL14.EGL_NO_DISPLAY) {
+            EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+            EGL14.eglDestroySurface(mEglDisplay, mEglSurface);
+            EGL14.eglTerminate(mEglDisplay);
+        }
+        mEglDisplay = EGL14.EGL_NO_DISPLAY;
+        mEglContext = EGL14.EGL_NO_CONTEXT;
+        mEglSurface = EGL14.EGL_NO_SURFACE;
+    }
+
+    @UiThreadTest
+    @Test
+    public void getDataSpace() {
+        mTex = new int[1];
+        glGenTextures(1, mTex, 0);
+
+        // create a surfaceTexture attached to mTex[0]
+        mSurfaceTexture = new SurfaceTexture(mTex[0]);
+        mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+        mSurface = new Surface(mSurfaceTexture);
+        mWriter = ImageWriter.newInstance(mSurface, 1);
+
+        long dataSpace = DataSpace.pack(DataSpace.STANDARD_BT709,
+                                        DataSpace.TRANSFER_SMPTE_170M,
+                                        DataSpace.RANGE_LIMITED);
+        Image inputImage = null;
+        try {
+            inputImage = mWriter.dequeueInputImage();
+            inputImage.setDataSpace(dataSpace);
+            assertEquals(dataSpace, inputImage.getDataSpace());
+
+            mWriter.queueInputImage(inputImage);
+
+            mSurfaceTexture.updateTexImage();
+            long outDataSpace = mSurfaceTexture.getDataSpace();
+
+            assertEquals(dataSpace, outDataSpace);
+            assertEquals(DataSpace.STANDARD_BT709, DataSpace.getStandard(outDataSpace));
+            assertEquals(DataSpace.TRANSFER_SMPTE_170M, DataSpace.getTransfer(outDataSpace));
+            assertEquals(DataSpace.RANGE_LIMITED, DataSpace.getRange(outDataSpace));
+        } finally {
+            if (inputImage != null) {
+                inputImage.close();
+                inputImage = null;
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void getDataSpaceWithoutSetDataSpace() {
+        mTex = new int[1];
+        glGenTextures(1, mTex, 0);
+
+        // create a surfaceTexture attached to mTex[0]
+        mSurfaceTexture = new SurfaceTexture(mTex[0]);
+        mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+        mSurface = new Surface(mSurfaceTexture);
+        mWriter = ImageWriter.newInstance(mSurface, 1);
+
+        Image inputImage = null;
+        try {
+            inputImage = mWriter.dequeueInputImage();
+            mWriter.queueInputImage(inputImage);
+
+            mSurfaceTexture.updateTexImage();
+
+            assertEquals(DataSpace.DATASPACE_UNKNOWN, mSurfaceTexture.getDataSpace());
+        } finally {
+            if (inputImage != null) {
+                inputImage.close();
+                inputImage = null;
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void getDataSpaceWithFormatYUV420_888() {
+        mTex = new int[1];
+        glGenTextures(1, mTex, 0);
+
+        // create a surfaceTexture attached to mTex[0]
+        mSurfaceTexture = new SurfaceTexture(mTex[0]);
+        mSurfaceTexture.setDefaultBufferSize(16, 16);
+
+        mSurface = new Surface(mSurfaceTexture);
+        mWriter = ImageWriter.newInstance(mSurface, 1, ImageFormat.YUV_420_888);
+
+        Image inputImage = null;
+        try {
+            inputImage = mWriter.dequeueInputImage();
+            mWriter.queueInputImage(inputImage);
+
+            mSurfaceTexture.updateTexImage();
+
+            // test default dataspace value of ImageFormat.YUV_420_888 format.
+            assertEquals(DataSpace.DATASPACE_JFIF, mSurfaceTexture.getDataSpace());
+        } finally {
+            if (inputImage != null) {
+                inputImage.close();
+                inputImage = null;
+            }
+        }
+    }
+
+    @UiThreadTest
+    @Test
+    public void getDataSpaceFromImageReaderNextImage() {
+        mReader = ImageReader.newInstance(100, 100, ImageFormat.YUV_420_888, 1);
+        mWriter = ImageWriter.newInstance(mReader.getSurface(), 1);
+
+        long dataSpace = DataSpace.pack(DataSpace.STANDARD_BT601_625,
+                                        DataSpace.TRANSFER_SMPTE_170M,
+                                        DataSpace.RANGE_FULL);
+
+        Image outputImage = null;
+        Image nextImage = null;
+        try {
+            outputImage = mWriter.dequeueInputImage();
+            outputImage.setDataSpace(dataSpace);
+
+            mWriter.queueInputImage(outputImage);
+
+            nextImage = mReader.acquireLatestImage();
+            assertEquals(dataSpace, nextImage.getDataSpace());
+        } finally {
+            if (outputImage != null) {
+                outputImage.close();
+                outputImage = null;
+            }
+            if (nextImage != null) {
+                nextImage.close();
+                nextImage = null;
+            }
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
index 9c0dfae..f74e666 100644
--- a/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/hdmi/cts/HdmiControlManagerTest.java
@@ -44,8 +44,11 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
@@ -321,6 +324,27 @@
     }
 
     @Test
+    public void testHdmiCecConfig_RoutingControl() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getRoutingControl();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_ROUTING_CONTROL)) {
+                mHdmiControlManager.setRoutingControl(value);
+                assertThat(mHdmiControlManager.getRoutingControl()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setRoutingControl(originalValue);
+            assertThat(mHdmiControlManager.getRoutingControl()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
     public void testHdmiCecConfig_HdmiCecVolumeControlEnabled() throws Exception {
         // Save original value
         int originalValue = mHdmiControlManager.getHdmiCecVolumeControlEnabled();
@@ -387,6 +411,27 @@
     }
 
     @Test
+    public void testHdmiCecConfig_SystemAudioControl() throws Exception {
+        // Save original value
+        int originalValue = mHdmiControlManager.getSystemAudioControl();
+        if (!mHdmiControlManager.getUserCecSettings().contains(
+                HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)) {
+            return;
+        }
+        try {
+            for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(
+                    HdmiControlManager.CEC_SETTING_NAME_SYSTEM_AUDIO_CONTROL)) {
+                mHdmiControlManager.setSystemAudioControl(value);
+                assertThat(mHdmiControlManager.getSystemAudioControl()).isEqualTo(value);
+            }
+        } finally {
+            // Restore original value
+            mHdmiControlManager.setSystemAudioControl(originalValue);
+            assertThat(mHdmiControlManager.getSystemAudioControl()).isEqualTo(originalValue);
+        }
+    }
+
+    @Test
     public void testHdmiCecConfig_SystemAudioModeMuting() throws Exception {
         // Save original value
         int originalValue = mHdmiControlManager.getSystemAudioModeMuting();
@@ -450,4 +495,59 @@
                     originalValue);
         }
     }
+
+    @Test
+    public void testHdmiCecConfig_SadsToQuery() throws Exception {
+        List<String> settings = new ArrayList<String>(Arrays.asList(
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_LPCM,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DD,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG1,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MP3,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MPEG2,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_AAC,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTS,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ATRAC,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_ONEBITAUDIO,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DDP,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DTSHD,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_TRUEHD,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_DST,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_WMAPRO,
+                HdmiControlManager.CEC_SETTING_NAME_QUERY_SAD_MAX));
+        // User-configurable settings
+        List<String> userConfigurableSettings = new ArrayList<>();
+        // Map from user-configurable settings to original values
+        Map<String, Integer> originalValues = new HashMap<>();
+        for (String setting : settings) {
+            if (mHdmiControlManager.getUserCecSettings().contains(setting)) {
+                userConfigurableSettings.add(setting);
+                originalValues.put(setting, mHdmiControlManager.getSadPresenceInQuery(setting));
+            }
+        }
+        if (userConfigurableSettings.size() == 0) {
+            return;
+        }
+        try {
+            for (String setting : userConfigurableSettings) {
+                for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(setting)) {
+                    mHdmiControlManager.setSadsPresenceInQuery(
+                            new ArrayList<String>(Arrays.asList(setting)), value);
+                    assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(value);
+                }
+            }
+            for (String setting : userConfigurableSettings) {
+                for (int value : mHdmiControlManager.getAllowedCecSettingIntValues(setting)) {
+                    mHdmiControlManager.setSadPresenceInQuery(setting, value);
+                    assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(value);
+                }
+            }
+        } finally {
+            // Restore original values
+            for (String setting : originalValues.keySet()) {
+                mHdmiControlManager.setSadPresenceInQuery(setting, originalValues.get(setting));
+                assertThat(mHdmiControlManager.getSadPresenceInQuery(setting)).isEqualTo(
+                        originalValues.get(setting));
+            }
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
index 1afad38..4200bc4 100755
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GoogleAtvReferenceRemoteControlTest.java
@@ -16,7 +16,6 @@
 
 package android.hardware.input.cts.tests;
 
-import android.content.Intent;
 import android.hardware.cts.R;
 import android.server.wm.WindowManagerStateHelper;
 
@@ -49,11 +48,6 @@
     public void testHomeKey() {
         testInputEvents(R.raw.google_atvreferenceremote_homekey);
         WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
-        Intent intent = new Intent();
-        intent.addCategory(Intent.CATEGORY_HOME);
-        intent.setAction(Intent.ACTION_MAIN);
-        mActivityRule.getActivity().startActivity(intent);
-
         wmStateHelper.waitForHomeActivityVisible();
         wmStateHelper.assertHomeActivityVisible(true);
     }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt b/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt
new file mode 100644
index 0000000..5672b81
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/GooglePixelUsbCEarbudsTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.input.cts.tests
+
+import android.hardware.cts.R
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+public class GooglePixelUsbCEarbudsTest : InputHidTestCase(R.raw.google_pixelusbcearbuds_register) {
+
+    @Test
+    fun testAllKeys() {
+        testInputEvents(R.raw.google_pixelusbcearbuds_keyeventtests)
+    }
+}
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 209b912..d19fb73 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
@@ -126,7 +126,9 @@
         assertEquals(mCurrentTestCase + " (action)",
                 expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
         assertSource(mCurrentTestCase, expectedKeyEvent, receivedKeyEvent);
-        assertEquals(mCurrentTestCase + " (keycode)",
+        assertEquals(mCurrentTestCase + " (keycode) expected: "
+                + KeyEvent.keyCodeToString(expectedKeyEvent.getKeyCode()) + " received: "
+                + KeyEvent.keyCodeToString(receivedKeyEvent.getKeyCode()),
                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
         assertMetaState(mCurrentTestCase, expectedKeyEvent.getMetaState(),
                 receivedKeyEvent.getMetaState());
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
deleted file mode 100644
index ec4e384..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputUinputTestCase.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import com.android.cts.input.UinputDevice;
-import com.android.cts.input.UinputTestData;
-
-import java.util.List;
-
-public class InputUinputTestCase extends InputTestCase {
-    private static final String TAG = "InputUinputTestCase";
-    private UinputDevice mUinputDevice;
-
-    InputUinputTestCase(int registerResourceId) {
-        super(registerResourceId);
-    }
-
-    @Override
-    protected void setUpDevice(int id, int vendorId, int productId, int sources,
-            String registerCommand) {
-        mUinputDevice = new UinputDevice(mInstrumentation, id, vendorId, productId, sources,
-                registerCommand);
-    }
-
-    @Override
-    protected void tearDownDevice() {
-        if (mUinputDevice != null) {
-            mUinputDevice.close();
-        }
-    }
-
-    @Override
-    protected void testInputDeviceEvents(int resourceId) {
-        List<UinputTestData> tests = mParser.getUinputTestData(resourceId);
-
-        for (UinputTestData testData: tests) {
-            mCurrentTestCase = testData.name;
-
-            // Send all of the evdev Events
-            for (int i = 0; i < testData.evdevEvents.size(); i++) {
-                final String injections = testData.evdevEvents.get(i);
-                mUinputDevice.injectEvents(injections);
-            }
-            verifyEvents(testData.events);
-        }
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
index d27c96c..a55c3b6 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerRaijuMobileBluetoothTest.java
@@ -16,7 +16,6 @@
 
 package android.hardware.input.cts.tests;
 
-import android.content.Intent;
 import android.hardware.cts.R;
 import android.server.wm.WindowManagerStateHelper;
 import android.view.KeyEvent;
@@ -55,12 +54,6 @@
     public void testHomeKey() throws Exception {
         mActivityRule.getActivity().addUnhandleKeyCode(KeyEvent.KEYCODE_BUTTON_MODE);
         testInputEvents(R.raw.razer_raiju_mobile_bluetooth_homekey);
-        // Put DUT on home screen
-        Intent intent = new Intent();
-        intent.addCategory(Intent.CATEGORY_HOME);
-        intent.setAction(Intent.ACTION_MAIN);
-        mActivityRule.getActivity().startActivity(intent);
-
 
         WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
 
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
index 1405530..030c030 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/RazerServalTest.java
@@ -16,7 +16,6 @@
 
 package android.hardware.input.cts.tests;
 
-import android.content.Intent;
 import android.hardware.cts.R;
 import android.server.wm.WindowManagerStateHelper;
 
@@ -55,12 +54,6 @@
     public void testHomeKey() {
         testInputEvents(R.raw.razer_serval_homekey);
         WindowManagerStateHelper wmStateHelper = new WindowManagerStateHelper();
-        // Put DUT on home screen
-        Intent intent = new Intent();
-        intent.addCategory(Intent.CATEGORY_HOME);
-        intent.setAction(Intent.ACTION_MAIN);
-        mActivityRule.getActivity().startActivity(intent);
-
 
         wmStateHelper.waitForHomeActivityVisible();
         wmStateHelper.assertHomeActivityVisible(true);
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index 94dd585..e6de053 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -16,6 +16,8 @@
 
 package android.keystore.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -42,6 +44,9 @@
 
 import com.google.common.collect.ObjectArrays;
 
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.security.AlgorithmParameters;
@@ -49,10 +54,10 @@
 import java.security.Key;
 import java.security.KeyStoreException;
 import java.security.Provider;
+import java.security.Provider.Service;
 import java.security.Security;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
-import java.security.Provider.Service;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Date;
@@ -74,9 +79,6 @@
 import javax.crypto.spec.PSource;
 import javax.crypto.spec.SecretKeySpec;
 
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 /**
  * Tests for algorithm-agnostic functionality of {@code Cipher} implementations backed by Android
  * Keystore.
@@ -1205,6 +1207,7 @@
                 fail("Importing auth bound keys to an insecure device should fail");
             } catch (KeyStoreException e) {
                 // Expected behavior
+                assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
             }
         }
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
index 4b66992..e61d2b3 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ImportWrappedKeyTest.java
@@ -25,16 +25,16 @@
 import static android.security.keymaster.KeymasterDefs.KM_PAD_PKCS7;
 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_DECRYPT;
 import static android.security.keymaster.KeymasterDefs.KM_PURPOSE_ENCRYPT;
-import static android.security.keystore.KeyProperties.PURPOSE_WRAP_KEY;
+
+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.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
-import android.content.pm.PackageManager;
 import android.keystore.cts.util.TestUtils;
-import android.os.SystemProperties;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.SecureKeyImportUnavailableException;
@@ -52,6 +52,8 @@
 import org.bouncycastle.asn1.DERSequence;
 import org.bouncycastle.asn1.DERSet;
 import org.bouncycastle.asn1.DERTaggedObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.security.Key;
 import java.security.KeyPair;
@@ -63,7 +65,6 @@
 import java.security.SecureRandom;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
-import java.security.spec.RSAKeyGenParameterSpec;
 import java.util.Arrays;
 
 import javax.crypto.Cipher;
@@ -74,16 +75,6 @@
 import javax.crypto.spec.PSource;
 import javax.crypto.spec.SecretKeySpec;
 
-import java.lang.Process;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.lang.InterruptedException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 @RunWith(AndroidJUnit4.class)
 public class ImportWrappedKeyTest {
     private static final String TAG = "ImportWrappedKeyTest";
@@ -146,20 +137,24 @@
     @Test
     public void testKeyStore_ImportWrappedKeyWrappingKeyMissing() throws Exception {
         final String EXPECTED_FAILURE = "Failed to import wrapped key. Keystore error code: 7";
-        String failureMessage = null;
+        KeyStoreException exception = null;
 
         try {
             byte [] fakeWrappedKey = new byte[1];
             importWrappedKey(fakeWrappedKey, WRAPPING_KEY_ALIAS + "_Missing");
         } catch (KeyStoreException e) {
-            failureMessage = e.getMessage();
+            exception = e;
+
         }
 
-        if (failureMessage == null) {
-            fail("Did not hit a failure but expected one");
-        }
+        assertWithMessage("Did not hit a failure but expected one").that(exception).isNotNull();
 
-        assertEquals(failureMessage, EXPECTED_FAILURE);
+        assertThat(exception.getMessage()).isEqualTo(EXPECTED_FAILURE);
+        assertThat(exception.getCause()).isInstanceOf(android.security.KeyStoreException.class);
+        android.security.KeyStoreException ksException =
+                (android.security.KeyStoreException) exception.getCause();
+        assertThat(ksException.getNumericErrorCode()).isEqualTo(
+                android.security.KeyStoreException.ERROR_KEY_DOES_NOT_EXIST);
     }
 
     @Test
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
index 3f83027..33b22ec 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyAttestationTest.java
@@ -46,7 +46,6 @@
 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;
@@ -54,7 +53,6 @@
 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
 import static org.hamcrest.Matchers.hasItems;
 import static org.hamcrest.Matchers.lessThanOrEqualTo;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -77,18 +75,22 @@
 import android.security.keystore.KeyProperties;
 import android.util.ArraySet;
 import android.util.Log;
+
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.RequiresDevice;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.google.common.collect.ImmutableSet;
 
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
 import java.io.File;
 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;
@@ -106,14 +108,9 @@
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+
 import javax.crypto.KeyGenerator;
 
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
 /**
  * Tests for Android KeysStore attestation.
  */
@@ -211,10 +208,11 @@
                                         curves[curveIndex], keySizes[curveIndex],
                                         purposes[purposeIndex], devicePropertiesAttestation);
                             } catch (Throwable e) {
-                                if (devicePropertiesAttestation
-                                        && (e.getCause() instanceof KeyStoreException)
-                                        && KM_ERROR_CANNOT_ATTEST_IDS ==
-                                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                                boolean isIdAttestationFailure =
+                                        (e.getCause() instanceof KeyStoreException)
+                                        && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+                                        == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+                                if (devicePropertiesAttestation && isIdAttestationFailure) {
                                     Log.i(TAG, "key attestation with device IDs not supported; "
                                             + "test skipped");
                                     continue;
@@ -232,6 +230,19 @@
         }
     }
 
+    private void assertPublicAttestationError(KeyStoreException keyStoreException,
+            boolean devicePropertiesAttestation) {
+        // Assert public failure information.
+        int errorCode = keyStoreException.getNumericErrorCode();
+        String assertMessage = String.format(
+                "Error code was %d, device properties attestation? %b",
+                errorCode, devicePropertiesAttestation);
+        assertTrue(assertMessage, KeyStoreException.ERROR_INCORRECT_USAGE == errorCode
+                || (devicePropertiesAttestation
+                && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE == errorCode));
+        assertFalse(keyStoreException.isTransientFailure());
+    }
+
     @Test
     public void testEcAttestation_TooLargeChallenge() throws Exception {
         if (!TestUtils.isAttestationSupported()) {
@@ -250,6 +261,7 @@
                         (devicePropertiesAttestation
                                 && KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
                 );
+                assertPublicAttestationError(cause, devicePropertiesAttestation);
             }
         }
     }
@@ -452,6 +464,9 @@
             // Attestation is expected to fail because of lack of permissions.
             KeyStoreException cause = (KeyStoreException) e.getCause();
             assertEquals(KM_ERROR_PERMISSION_DENIED, cause.getErrorCode());
+            // Assert public failure information.
+            assertEquals(KeyStoreException.ERROR_PERMISSION_DENIED, cause.getNumericErrorCode());
+            assertFalse(cause.isTransientFailure());
         } finally {
             KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
             keyStore.load(null);
@@ -547,6 +562,7 @@
                         (devicePropertiesAttestation
                                 && KM_ERROR_CANNOT_ATTEST_IDS == cause.getErrorCode())
                 );
+                assertPublicAttestationError(cause, devicePropertiesAttestation);
             }
         }
     }
@@ -685,9 +701,11 @@
                 testRsaAttestation(challenge, false /* includeValidityDates */, keySize, purpose,
                         paddings, devicePropertiesAttestation);
             } catch (Throwable e) {
-                if (devicePropertiesAttestation && (e.getCause() instanceof KeyStoreException)
-                        && KM_ERROR_CANNOT_ATTEST_IDS ==
-                                ((KeyStoreException) e.getCause()).getErrorCode()) {
+                boolean isIdAttestationFailure =
+                        (e.getCause() instanceof KeyStoreException)
+                                && KeyStoreException.ERROR_ID_ATTESTATION_FAILURE
+                                == ((KeyStoreException) e.getCause()).getNumericErrorCode();
+                if (devicePropertiesAttestation && isIdAttestationFailure) {
                     Log.i(TAG, "key attestation with device IDs not supported; test skipped");
                     continue;
                 }
@@ -1191,7 +1209,9 @@
         assertTrue("Verified boot key is only " + rootOfTrust.getVerifiedBootKey().length +
                    " bytes long", rootOfTrust.getVerifiedBootKey().length >= 32);
         if (requireLocked) {
-            assertTrue(rootOfTrust.isDeviceLocked());
+            final String unlockedDeviceMessage = "The device's bootloader must be locked. This may "
+                    + "not be the default for pre-production devices.";
+            assertTrue(unlockedDeviceMessage, rootOfTrust.isDeviceLocked());
             checkEntropy(rootOfTrust.getVerifiedBootKey());
             assertEquals(KM_VERIFIED_BOOT_VERIFIED, rootOfTrust.getVerifiedBootState());
         }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
new file mode 100644
index 0000000..515d0e5
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyStoreExceptionTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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 org.junit.Assert.assertTrue;
+
+import android.security.KeyStoreException;
+import android.security.keymaster.KeymasterDefs;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidJUnit4.class)
+public class KeyStoreExceptionTest {
+    @Test
+    public void testAllKeymasterDefsAreCovered() throws IllegalAccessException {
+        ImmutableList<Field> kmErrors = getKeymasterDefsFields();
+        assertTrue("Test bug: there should be errors to look up",
+                kmErrors.size() > 0);
+
+        for (Field f : kmErrors) {
+            assertTrue(String.format("Missing entry for field %s", f.getName()),
+                    KeyStoreException.hasFailureInfoForError(f.getInt(null)));
+        }
+    }
+
+    public static ImmutableList<Field> getKeymasterDefsFields() {
+        ImmutableList.Builder<Field> errorFieldsBuilder = new ImmutableList.Builder<>();
+
+        Class kmDefsClass = KeymasterDefs.class;
+        for (Field f : kmDefsClass.getDeclaredFields()) {
+            if (f.getName().startsWith("KM_ERROR") && f.getType().equals(int.class)) {
+                errorFieldsBuilder.add(f);
+            }
+        }
+
+        return errorFieldsBuilder.build();
+    }
+}
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index eed4fc3..954f3a2 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -26,6 +26,8 @@
     libs: ["android.test.base"],
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 3febb32..b9db7d2 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -26,6 +26,8 @@
     libs: ["android.test.base"],
     srcs: ["src/**/*.java"],
     sdk_version: "current",
+    min_sdk_version: "31",
+    target_sdk_version: "31",
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/match_flags/OWNERS b/tests/tests/match_flags/OWNERS
index 8a44fb2..d1eff73 100644
--- a/tests/tests/match_flags/OWNERS
+++ b/tests/tests/match_flags/OWNERS
@@ -2,4 +2,4 @@
 patb@google.com
 toddke@google.com
 chiuwinson@google.com
-rtmitchell@google.com
+zyy@google.com
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 267f456..86f744f 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -39,6 +39,7 @@
 
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
     <uses-permission android:name="android.permission.VIBRATE"/>
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index 23cc264..8d20655 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -19,7 +19,6 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
         <option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
         <option name="set-test-harness" value="false" />
@@ -55,7 +54,6 @@
         <!-- test-timeout unit is ms, value = 30 min -->
         <option name="test-timeout" value="1800000" />
         <option name="runtime-hint" value="4h" />
-        <option name="exclude-annotation" value="org.junit.Ignore" />
         <option name="hidden-api-checks" value="false" />
         <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
         <option name="isolated-storage" value="false" />
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 6775f87..f99958f 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -2,14 +2,91 @@
 include ../../media/OWNERS
 elaurent@google.com
 etalvala@google.com
-hdmoon@google.com
 hunga@google.com
-insun@google.com
-jaewan@google.com
-jinpark@google.com
 jmtrivi@google.com
 jsharkey@android.com
-sungsoo@google.com
 
-# go/android-fwk-media-solutions for info on areas of ownership.
-include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
+per-file src/android/media/cts/AdaptivePlaybackTest.java = hunga@google.com
+per-file src/android/media/cts/AudioMetadataTest.java = hunga@google.com
+per-file src/android/media/cts/AudioRecordTest.java = hunga@google.com
+per-file src/android/media/cts/AudioRecord_BufferSizeTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackLatencyTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackOffloadTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackSurroundTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrackTest.java = hunga@google.com
+per-file src/android/media/cts/AudioTrack_ListenerTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolAacTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolHapticTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolMidiTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolOggTest.java = hunga@google.com
+per-file src/android/media/cts/SoundPoolTest.java = hunga@google.com
+per-file src/android/media/cts/VolumeShaperTest.java = hunga@google.com
+
+per-file src/android/media/cts/AudioAttributesTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioFocusTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioPlaybackConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/cts/AudioRecordingConfigurationTest.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestAacDrc.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestAacFormat.java = jmtrivi@google.com
+per-file src/android/media/cts/DecoderTestXheAac.java = jmtrivi@google.com
+per-file src/android/media/cts/LoudnessEnhancerTest.java = jmtrivi@google.com
+per-file src/android/media/cts/RingtoneManagerTest.java = jmtrivi@google.com
+per-file src/android/media/cts/RingtoneTest.java = jmtrivi@google.com
+
+per-file src/android/media/cts/AudioNativeTest.java = philburk@google.com
+per-file src/android/media/cts/AudioPlayRoutingNative.java = philburk@google.com
+per-file src/android/media/cts/AudioRecordNative.java = philburk@google.com
+per-file src/android/media/cts/AudioRecordRoutingNative.java = philburk@google.com
+per-file src/android/media/cts/AudioTrackNative.java = philburk@google.com
+per-file src/android/media/cts/MidiSoloTest.java = philburk@google.com
+
+per-file src/android/media/cts/DecodeAccuracyTest.java = lajos@google.com
+per-file src/android/media/cts/DecodeEditEncodeTest.java = lajos@google.com
+per-file src/android/media/cts/DecoderConformanceTest.java = lajos@google.com
+per-file src/android/media/cts/DecoderTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeDecodeTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeVirtualDisplayTest.java = lajos@google.com
+per-file src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java = lajos@google.com
+per-file src/android/media/cts/EncoderTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecBlockModelTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecCapabilitiesTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecListTest.java = lajos@google.com
+per-file src/android/media/cts/MediaCodecTest.java = lajos@google.com
+per-file src/android/media/cts/NativeDecoderTest.java = lajos@google.com
+per-file src/android/media/cts/NativeImageReaderTest.java = lajos@google.com
+per-file src/android/media/cts/ResourceManagerTest.java = lajos@google.com
+per-file src/android/media/cts/NativeDecoderTest.java = lajos@google.com
+per-file src/android/media/cts/VideoCodecTest.java = lajos@google.com
+per-file src/android/media/cts/VideoDecoderPerfTest.java = lajos@google.com
+per-file src/android/media/cts/VideoDecoderRotationTest.java = lajos@google.com
+per-file src/android/media/cts/VideoEncoderTest.java = lajos@google.com
+per-file src/android/media/cts/VideoCodecTest.java = lajos@google.com
+
+per-file src/android/media/cts/MediaBrowserServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaBrowserTest.java = olly@google.com
+per-file src/android/media/cts/MediaCasTest.java = olly@google.com
+per-file src/android/media/cts/MediaCommunicationManagerTest.java = olly@google.com
+per-file src/android/media/cts/MediaController2Test.java = olly@google.com
+per-file src/android/media/cts/MediaControllerTest.java = olly@google.com
+per-file src/android/media/cts/MediaExtractorTest.java = olly@google.com
+per-file src/android/media/cts/MediaFormatTest.java = olly@google.com
+per-file src/android/media/cts/MediaMetadataRetrieverTest.java = olly@google.com
+per-file src/android/media/cts/MediaMetadataTest.java = olly@google.com
+per-file src/android/media/cts/MediaMuxerTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerFlakyNetworkTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerSurfaceTest.java = olly@google.com
+per-file src/android/media/cts/MediaPlayerTest.java = olly@google.com
+per-file src/android/media/cts/MediaMuxerTest.java = olly@google.com
+per-file src/android/media/cts/MediaRecorderTest.java = olly@google.com
+per-file src/android/media/cts/MediaRoute2InfoTest.java = olly@google.com
+per-file src/android/media/cts/MediaRoute2ProviderServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaRouter2Test.java = olly@google.com
+per-file src/android/media/cts/MediaRouterTest.java = olly@google.com
+per-file src/android/media/cts/MediaSession2ServiceTest.java = olly@google.com
+per-file src/android/media/cts/MediaSession2Test.java = olly@google.com
+per-file src/android/media/cts/MediaSessionManagerTest.java = olly@google.com
+per-file src/android/media/cts/MediaSessionTest.java = olly@google.com
+per-file src/android/media/cts/StreamingMediaPlayerTest.java = olly@google.com
+
+per-file src/android/media/cts/CamcorderProfileTest.java = etalvala@google.com
+per-file src/android/media/cts/CameraProfileTest.java = etalvala@google.com
diff --git a/tests/tests/media/audio/Android.bp b/tests/tests/media/audio/Android.bp
index e7f1363..749a231 100644
--- a/tests/tests/media/audio/Android.bp
+++ b/tests/tests/media/audio/Android.bp
@@ -40,9 +40,11 @@
         "jni/audio-track-native.cpp",
         "jni/sl-utils.cpp",
     ],
-    include_dirs: ["frameworks/wilhelm/include",
-                   "frameworks/wilhelm/src/android",
-                   "system/core/include"],
+    include_dirs: [
+        "frameworks/wilhelm/include",
+        "frameworks/wilhelm/src/android",
+        "system/core/include",
+    ],
     shared_libs: [
         "libandroid",
         "liblog",
@@ -86,6 +88,8 @@
     resource_dirs: ["res"],
     srcs: [
         "src/**/*.java",
+        "src/**/*.kt",
+        "aidl/**/*.aidl",
     ],
     platform_apis: true,
     jni_uses_sdk_apis: true,
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
index 8d8d000..6499337 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioAttributesTest.java
@@ -90,6 +90,15 @@
         }
     }
 
+    // Test case 4: verify the Ultrasound content APIs for AudioAttributes
+    public void testSetUltrasoundContentType() throws Exception {
+        final AudioAttributes internalContentApiAttr = new AudioAttributes.Builder()
+                .setInternalContentType(AudioAttributes.CONTENT_TYPE_ULTRASOUND)
+                .build();
+
+        assertEquals("Ultrasound by setInternalContentType doesn't match",
+                internalContentApiAttr.getContentType(), AudioAttributes.CONTENT_TYPE_ULTRASOUND);
+    }
     // -----------------------------------------------------------------
     // Builder tests
     // ----------------------------------
@@ -208,6 +217,34 @@
     }
 
     // -----------------------------------------------------------------
+    // Deprecation tests
+    // ----------------------------------
+    private int[] DEPRECATED_USAGES = { AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+            AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+            AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT };
+
+    public void testDeprecationNotificationUsagesBuilder() throws Exception {
+        for (int deprecatedNotifUsage : DEPRECATED_USAGES) {
+            final AudioAttributes aa = new AudioAttributes.Builder()
+                    .setUsage(deprecatedNotifUsage)
+                    .build();
+            assertEquals("Deprecated notification usage value not remapped",
+                    AudioAttributes.USAGE_NOTIFICATION, aa.getUsage());
+        }
+    }
+
+    public void testDeprecationNotificationUsagesCopyBuilder() throws Exception {
+        for (int deprecatedNotifUsage : DEPRECATED_USAGES) {
+            final AudioAttributes aa = new AudioAttributes.Builder()
+                    .setUsage(deprecatedNotifUsage)
+                    .build();
+            final AudioAttributes copy = new AudioAttributes.Builder(aa).build();
+            assertEquals("Deprecated notification usage value not remapped",
+                    AudioAttributes.USAGE_NOTIFICATION, copy.getUsage());
+        }
+    }
+
+    // -----------------------------------------------------------------
     // Regression tests
     // ----------------------------------
     // Test against regression where setLegacyStreamType() was creating a different Builder
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt b/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt
new file mode 100644
index 0000000..9e3231f
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioDevicesForAttributesTest.kt
@@ -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.media.audio.cts
+
+import android.content.pm.PackageManager
+import android.media.AudioAttributes
+import android.media.AudioDeviceInfo
+import android.media.AudioManager
+import android.media.cts.NonMediaMainlineTest
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@NonMediaMainlineTest
+@RunWith(AndroidJUnit4::class)
+class AudioDevicesForAttributesTest {
+    /**
+     * Test that getAudioDevicesForAttributes reports the output audio device(s)
+     */
+    @Test
+    fun testGetAudioDevicesForAttributes() {
+        val context = InstrumentationRegistry.getInstrumentation().context
+        assumeTrue(
+            context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)
+        )
+
+        val audioManager = context.getSystemService(AudioManager::class.java)
+        val allOutDevices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
+        var hasAtLeastOneDeviceForAttributes = false
+
+        for (usage in AudioAttributes.getSdkUsages()) {
+            val audioAttributes = AudioAttributes.Builder()
+                .setUsage(usage)
+                .build()
+
+            val audioDevicesForAttributes: List<AudioDeviceInfo> =
+                audioManager.getAudioDevicesForAttributes(audioAttributes)
+
+            assertTrue(
+                "Unknown device for attributes!",
+                allOutDevices.toList().containsAll(audioDevicesForAttributes)
+            )
+
+            if (audioDevicesForAttributes.isNotEmpty()) {
+                hasAtLeastOneDeviceForAttributes = true
+            }
+        }
+
+        if (allOutDevices.isNotEmpty()) {
+            assertTrue(
+                "No device for any AudioAttributes, though output device exists.",
+                hasAtLeastOneDeviceForAttributes
+            )
+        }
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
index f84e680..c9b107c 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioFocusTest.java
@@ -57,6 +57,7 @@
             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
             .build();
 
+    private static final String TEST_CALL_ID = "fake call";
 
     public void testInvalidAudioFocusRequestDelayNoListener() throws Exception {
         AudioFocusRequest req = null;
@@ -215,10 +216,160 @@
         // verify a request that is "force duck"'d still causes loss of focus because it doesn't
         // come from an A11y service, and requests are from same uid
         final AudioAttributes[] attributes = {ATTR_MEDIA, ATTR_A11Y};
-        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, attributes,
                 false /*no handler*/, true /* forceDucking */);
     }
 
+    public void testAudioFocusRequestA11y() throws Exception {
+        final AudioAttributes[] attributes = {ATTR_DRIVE_DIR, ATTR_A11Y};
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE,
+                AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, attributes,
+                false /*no handler*/, false /* forceDucking */);
+    }
+
+    /**
+     * Test delayed focus behaviors with the sequence:
+     * 1/ media requests FOCUS_GAIN
+     * 2/ (simulated) call with focus lock: media gets FOCUS_LOSS_TRANSIENT
+     * 3/ drive dir requests FOCUS_GAIN + delay OK: is delayed + media gets FOCUS_LOSS
+     * 4/ call ends: drive dir gets FOCUS_GAIN
+     * @throws Exception when failing
+     */
+    public void testAudioFocusDelayedByCall() throws Exception {
+        Log.i(TAG, "testAudioFocusDelayedByCall");
+        final AudioManager am = new AudioManager(getContext());
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler handler = new Handler(handlerThread.getLooper());
+
+        final AudioFocusRequest callFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+                        .setLocksFocus(true).build();
+        final FocusChangeListener mediaListener = new FocusChangeListener();
+        final AudioFocusRequest mediaFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                        .setAudioAttributes(ATTR_MEDIA)
+                        .setOnAudioFocusChangeListener(mediaListener, handler)
+                        .build();
+        final FocusChangeListener driveListener = new FocusChangeListener();
+        final AudioFocusRequest driveFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                        .setAudioAttributes(ATTR_DRIVE_DIR)
+                        .setAcceptsDelayedFocusGain(true)
+                        .setOnAudioFocusChangeListener(driveListener, handler)
+                        .build();
+
+        // for focus request/abandon test methods
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.QUERY_AUDIO_STATE);
+        try {
+            // media requests audio focus
+            int res = am.requestAudioFocus(mediaFocusReq);
+            assertEquals("media request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            // call requests audio focus
+            am.requestAudioFocusForTest(callFocusReq, TEST_CALL_ID, 1977, Build.VERSION_CODES.S);
+            assertEquals("call request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            // verify media lost focus with LOSS_TRANSIENT
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched to media after call start",
+                    AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mediaListener.getFocusChangeAndReset());
+            // drive dir requests audio focus, verify it's delayed
+            res = am.requestAudioFocus(driveFocusReq);
+            assertEquals("Focus request from drive dir. wasn't delayed",
+                    AudioManager.AUDIOFOCUS_REQUEST_DELAYED, res);
+            // verify media lost focus with LOSS as it's being kicked out of the focus stack
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched to media after drive dir delayed focus",
+                    AudioManager.AUDIOFOCUS_LOSS, mediaListener.getFocusChangeAndReset());
+            // end the call, verify drive dir gets focus
+            am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus gain not dispatched to drive dir after call",
+                    AudioManager.AUDIOFOCUS_GAIN, driveListener.getFocusChangeAndReset());
+        } finally {
+            am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+            am.abandonAudioFocusRequest(driveFocusReq);
+            am.abandonAudioFocusRequest(mediaFocusReq);
+            handler.getLooper().quit();
+            handlerThread.quitSafely();
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
+
+    /**
+     * Test delayed focus behaviors with the sequence:
+     * 1/ media requests FOCUS_GAIN
+     * 2/ (simulated) call with focus lock: media gets FOCUS_LOSS_TRANSIENT
+     * 3/ drive dir requests AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK + delay OK: is delayed
+     * 4/ call ends: drive dir gets FOCUS_GAIN
+     * 5/ drive dir ends: media gets FOCUS_GAIN (because it was still in the stack,
+     *                    unlike in testAudioFocusDelayedByCall)
+     * @throws Exception when failing
+     */
+    public void testAudioFocusTransientDelayedByCall() throws Exception {
+        Log.i(TAG, "testAudioFocusDelayedByCall");
+        final AudioManager am = new AudioManager(getContext());
+        final HandlerThread handlerThread = new HandlerThread(TAG);
+        handlerThread.start();
+        final Handler handler = new Handler(handlerThread.getLooper());
+
+        final AudioFocusRequest callFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT)
+                        .setLocksFocus(true).build();
+        final FocusChangeListener mediaListener = new FocusChangeListener();
+        final AudioFocusRequest mediaFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
+                        .setAudioAttributes(ATTR_MEDIA)
+                        .setOnAudioFocusChangeListener(mediaListener, handler)
+                        .build();
+        final FocusChangeListener driveListener = new FocusChangeListener();
+        final AudioFocusRequest driveFocusReq =
+                new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)
+                        .setAudioAttributes(ATTR_DRIVE_DIR)
+                        .setAcceptsDelayedFocusGain(true)
+                        .setOnAudioFocusChangeListener(driveListener, handler)
+                        .build();
+
+        // for focus request/abandon test methods
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+                Manifest.permission.QUERY_AUDIO_STATE);
+        try {
+            // media requests audio focus
+            int res = am.requestAudioFocus(mediaFocusReq);
+            assertEquals("media request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            // call requests audio focus
+            am.requestAudioFocusForTest(callFocusReq, TEST_CALL_ID, 1977, Build.VERSION_CODES.S);
+            assertEquals("call request failed", AudioManager.AUDIOFOCUS_REQUEST_GRANTED, res);
+            // verify media lost focus with LOSS_TRANSIENT
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus loss not dispatched to media after call start",
+                    AudioManager.AUDIOFOCUS_LOSS_TRANSIENT, mediaListener.getFocusChangeAndReset());
+            // drive dir requests audio focus, verify it's delayed
+            res = am.requestAudioFocus(driveFocusReq);
+            assertEquals("Focus request from drive dir. wasn't delayed",
+                    AudioManager.AUDIOFOCUS_REQUEST_DELAYED, res);
+            // end the call, verify drive dir gets focus, and media didn't get focus change
+            am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus gain not dispatched to drive dir after call",
+                    AudioManager.AUDIOFOCUS_GAIN, driveListener.getFocusChangeAndReset());
+            assertEquals("Focus change was dispatched to media",
+                    AudioManager.AUDIOFOCUS_NONE, mediaListener.getFocusChangeAndReset());
+            // end the drive dir, verify media gets focus
+            am.abandonAudioFocusRequest(driveFocusReq);
+            Thread.sleep(TEST_TIMING_TOLERANCE_MS);
+            assertEquals("Focus gain not dispatched to media after drive dir",
+                    AudioManager.AUDIOFOCUS_GAIN, mediaListener.getFocusChangeAndReset());
+        } finally {
+            am.abandonAudioFocusForTest(callFocusReq, TEST_CALL_ID);
+            am.abandonAudioFocusRequest(driveFocusReq);
+            am.abandonAudioFocusRequest(mediaFocusReq);
+            handler.getLooper().quit();
+            handlerThread.quitSafely();
+            getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+        }
+    }
     /**
      * Determine if automotive feature is available
      * @param context context to query
@@ -438,35 +589,38 @@
      */
     private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
             boolean useHandlerInListener) throws Exception {
-        doTestTwoPlayersGainLoss(gainType, attributes, useHandlerInListener,
-                false /*forceDucking*/);
+        doTestTwoPlayersGainLoss(AudioManager.AUDIOFOCUS_GAIN, gainType, attributes,
+                useHandlerInListener, false /*forceDucking*/);
     }
 
     /**
      * Same as {@link #doTestTwoPlayersGainLoss(int, AudioAttributes[], boolean)} with forceDucking
      *   set to false.
-     * @param gainType
-     * @param attributes
+     * @param gainTypeForFirstPlayer focus gain of the focus owner on bottom (== 1st focus request)
+     * @param gainTypeForSecondPlayer focus gain of the focus owner on top (== 2nd focus request)
+     * @param attributes Audio attributes for first and second player, in order.
      * @param useHandlerInListener
      * @param forceDucking value used for setForceDucking in request for focus requester at top of
      *   stack (second requester in test).
      * @throws Exception
      */
-    private void doTestTwoPlayersGainLoss(int gainType, AudioAttributes[] attributes,
-            boolean useHandlerInListener, boolean forceDucking) throws Exception {
+    private void doTestTwoPlayersGainLoss(int gainTypeForFirstPlayer, int gainTypeForSecondPlayer,
+            AudioAttributes[] attributes, boolean useHandlerInListener,
+            boolean forceDucking) throws Exception {
         final int NB_FOCUS_OWNERS = 2;
         if (NB_FOCUS_OWNERS != attributes.length) {
             throw new IllegalArgumentException("Invalid test: invalid number of attributes");
         }
         final AudioFocusRequest[] focusRequests = new AudioFocusRequest[NB_FOCUS_OWNERS];
         final FocusChangeListener[] focusListeners = new FocusChangeListener[NB_FOCUS_OWNERS];
-        final int[] focusGains = { AudioManager.AUDIOFOCUS_GAIN, gainType };
+        final int[] focusGains = { gainTypeForFirstPlayer, gainTypeForSecondPlayer };
         int expectedLoss = 0;
-        switch (gainType) {
+        switch (gainTypeForSecondPlayer) {
             case AudioManager.AUDIOFOCUS_GAIN:
                 expectedLoss = AudioManager.AUDIOFOCUS_LOSS;
                 break;
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
+            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
                 expectedLoss = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
                 break;
             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
@@ -521,7 +675,7 @@
             focusRequests[1] = null;
             Thread.sleep(TEST_TIMING_TOLERANCE_MS);
             // when focus was lost because it was requested with GAIN, focus is not given back
-            if (gainType != AudioManager.AUDIOFOCUS_GAIN) {
+            if (gainTypeForSecondPlayer != AudioManager.AUDIOFOCUS_GAIN) {
                 assertEquals("Focus gain not dispatched", AudioManager.AUDIOFOCUS_GAIN,
                         focusListeners[0].getFocusChangeAndReset());
             } else {
@@ -581,6 +735,7 @@
 
         @Override
         public void onAudioFocusChange(int focusChange) {
+            Log.i(TAG, "onAudioFocusChange:" + focusChange + " listener:" + this);
             synchronized (mLock) {
                 mFocusChange = focusChange;
             }
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
index fa7308f..19ce174 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
@@ -16,8 +16,6 @@
 
 package android.media.audio.cts;
 
-import static org.junit.Assert.assertNotEquals;
-
 import static android.media.AudioManager.ADJUST_LOWER;
 import static android.media.AudioManager.ADJUST_RAISE;
 import static android.media.AudioManager.ADJUST_SAME;
@@ -42,8 +40,11 @@
 import static android.media.AudioManager.VIBRATE_SETTING_ONLY_SILENT;
 import static android.media.AudioManager.VIBRATE_TYPE_NOTIFICATION;
 import static android.media.AudioManager.VIBRATE_TYPE_RINGER;
+import static android.provider.Settings.Global.APPLY_RAMPING_RINGER;
 import static android.provider.Settings.System.SOUND_EFFECTS_ENABLED;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.Manifest;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
@@ -54,12 +55,13 @@
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.media.AudioAttributes;
+import android.media.AudioDescriptor;
 import android.media.AudioDeviceAttributes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioProfile;
-import android.media.AudioDescriptor;
+import android.media.AudioTrack;
 import android.media.MediaPlayer;
 import android.media.MediaRecorder;
 import android.media.MicrophoneInfo;
@@ -123,6 +125,15 @@
             add(AudioDescriptor.STANDARD_NONE);
             add(AudioDescriptor.STANDARD_EDID);
     }};
+    private static final HashMap<Integer, Integer> DIRECT_OFFLOAD_MAP = new HashMap<>() {{
+            put(AudioManager.PLAYBACK_OFFLOAD_NOT_SUPPORTED,
+                    AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED);
+            put(AudioManager.PLAYBACK_OFFLOAD_SUPPORTED,
+                    AudioManager.DIRECT_PLAYBACK_OFFLOAD_SUPPORTED);
+            put(AudioManager.PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED,
+                    AudioManager.DIRECT_PLAYBACK_OFFLOAD_GAPLESS_SUPPORTED);
+        }};
+    private static final int INVALID_DIRECT_PLAYBACK_MODE = -1;
     private AudioManager mAudioManager;
     private NotificationManager mNm;
     private boolean mHasVibrator;
@@ -677,6 +688,33 @@
         }
     }
 
+    public void testAccessRampingRinger() {
+        boolean originalEnabledState = mAudioManager.isRampingRingerEnabled();
+        try {
+            mAudioManager.setRampingRingerEnabled(false);
+            assertFalse(mAudioManager.isRampingRingerEnabled());
+
+            mAudioManager.setRampingRingerEnabled(true);
+            assertTrue(mAudioManager.isRampingRingerEnabled());
+        } finally {
+            mAudioManager.setRampingRingerEnabled(originalEnabledState);
+        }
+    }
+
+    public void testRampingRingerSetting() {
+        boolean originalEnabledState = mAudioManager.isRampingRingerEnabled();
+        try {
+            // Deprecated public setting should still be supported and affect the setting getter.
+            Settings.Global.putInt(mContext.getContentResolver(), APPLY_RAMPING_RINGER, 0);
+            assertFalse(mAudioManager.isRampingRingerEnabled());
+
+            Settings.Global.putInt(mContext.getContentResolver(), APPLY_RAMPING_RINGER, 1);
+            assertTrue(mAudioManager.isRampingRingerEnabled());
+        } finally {
+            mAudioManager.setRampingRingerEnabled(originalEnabledState);
+        }
+    }
+
     public void testVolume() throws Exception {
         if (MediaUtils.check(mIsTelevision, "No volume test due to fixed/full vol devices"))
             return;
@@ -1897,6 +1935,53 @@
         }
     }
 
+    public void testGetDirectPlaybackSupport() {
+        assertEquals(AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED,
+                AudioManager.getDirectPlaybackSupport(
+                        new AudioFormat.Builder().build(),
+                        new AudioAttributes.Builder().build()));
+        AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        AudioAttributes attr = new AudioAttributes.Builder()
+                .setUsage(AudioAttributes.USAGE_MEDIA)
+                .setLegacyStreamType(STREAM_MUSIC).build();
+        for (AudioDeviceInfo device : devices) {
+            for (int encoding : device.getEncodings()) {
+                for (int channelMask : device.getChannelMasks()) {
+                    for (int sampleRate : device.getSampleRates()) {
+                        AudioFormat format = new AudioFormat.Builder()
+                                .setEncoding(encoding)
+                                .setChannelMask(channelMask)
+                                .setSampleRate(sampleRate).build();
+                        final int directPlaybackSupport =
+                                AudioManager.getDirectPlaybackSupport(format, attr);
+                        assertEquals(
+                                AudioTrack.isDirectPlaybackSupported(format, attr),
+                                directPlaybackSupport
+                                        != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED);
+                        if (directPlaybackSupport == AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
+                            assertEquals(
+                                    DIRECT_OFFLOAD_MAP.getOrDefault(
+                                            AudioManager.getPlaybackOffloadSupport(format, attr),
+                                            INVALID_DIRECT_PLAYBACK_MODE).intValue(),
+                                    directPlaybackSupport);
+                        } else if ((directPlaybackSupport
+                                & AudioManager.DIRECT_PLAYBACK_OFFLOAD_SUPPORTED)
+                                != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) {
+                            // AudioManager.getPlaybackOffloadSupport can only query offload
+                            // support but not other direct support like passthrough.
+                            assertNotEquals(
+                                    AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED,
+                                    DIRECT_OFFLOAD_MAP.getOrDefault(
+                                            AudioManager.getPlaybackOffloadSupport(format, attr),
+                                            AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED)
+                                            & directPlaybackSupport);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     private void assertStreamVolumeEquals(int stream, int expectedVolume) throws Exception {
         assertStreamVolumeEquals(stream, expectedVolume,
                 "Unexpected stream volume for stream=" + stream);
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
index 1fbabbf..1061ae1 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioNativeTest.java
@@ -26,6 +26,7 @@
 import android.media.cts.AudioHelper;
 import android.media.cts.NonMediaMainlineTest;
 import android.os.Build;
+import android.platform.test.annotations.Presubmit;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.CtsAndroidTestCase;
@@ -43,6 +44,7 @@
         nativeAppendixBBufferQueue();
     }
 
+    @Presubmit
     public void testAppendixBRecording() {
         // better to detect presence of microphone here.
         if (!hasMicrophone()) {
@@ -51,12 +53,14 @@
         nativeAppendixBRecording();
     }
 
+    @Presubmit
     public void testStereo16Playback() {
         assertTrue(AudioTrackNative.test(
                 2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
                 20 /* msecPerBuffer */, 8 /* numBuffers */));
     }
 
+    @Presubmit
     public void testStereo16Record() {
         if (!hasMicrophone()) {
             return;
@@ -209,6 +213,7 @@
         }
     }
 
+    @Presubmit
     public void testRecordAudit() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -218,6 +223,7 @@
                 1000 /* segmentDurationMs */, 10 /* numSegments */);
     }
 
+    @Presubmit
     public void testOutputChannelMasks() {
         if (!hasAudioOutput()) {
             return;
@@ -242,6 +248,7 @@
         }
     }
 
+    @Presubmit
     public void testInputChannelMasks() {
         if (!hasMicrophone()) {
             return;
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
index 4ab67a5..9171312 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioPlaybackCaptureTest.java
@@ -275,6 +275,7 @@
     private static final boolean EXPECT_DATA = true;
     private static final boolean EXPECT_SILENCE = false;
 
+    // We have explicit tests per usage testCaptureMatchingAllowedUsage*
     private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
             AudioAttributes.USAGE_UNKNOWN,
             AudioAttributes.USAGE_MEDIA,
@@ -323,17 +324,30 @@
         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
     }
 
+    // Allowed usage tests done individually to isolate failure and keep test duration < 30s.
     @Test
-    public void testCaptureMatchingAllowedUsage() throws Exception {
-        for (int usage : ALLOWED_USAGES) {
-            mAPCTestConfig.matchingUsages = new int[]{ usage };
-            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
-            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+    public void testCaptureMatchingAllowedUsageUnknown() throws Exception {
+        doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_UNKNOWN);
+    }
 
-            mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
-            testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
-            testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
-        }
+    @Test
+    public void testCaptureMatchingAllowedUsageMedia() throws Exception {
+        doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_MEDIA);
+    }
+
+    @Test
+    public void testCaptureMatchingAllowedUsageGame() throws Exception {
+        doTestCaptureMatchingAllowedUsage(AudioAttributes.USAGE_GAME);
+    }
+
+    private void doTestCaptureMatchingAllowedUsage(int usage) throws Exception {
+        mAPCTestConfig.matchingUsages = new int[]{ usage };
+        testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+        testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+
+        mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+        testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+        testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
     }
 
     @Test
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
index ee5f0da..95fbc81 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioRecordTest.java
@@ -73,7 +73,7 @@
 import java.util.ArrayList;
 import java.util.concurrent.Executor;
 import java.util.List;
-
+import java.util.function.BiFunction;
 
 @NonMediaMainlineTest
 @RunWith(AndroidJUnit4.class)
@@ -180,7 +180,7 @@
             return;
         }
         final int SLEEP_TIME = 10;
-        final int RECORD_TIME = 10000;
+        final int RECORD_TIME = 5000;
         assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState());
 
         int markerInFrames = mAudioRecord.getSampleRate() / 2;
@@ -1009,7 +1009,7 @@
             boolean useByteBuffer, boolean blocking,
             final boolean auditRecording, final boolean isChannelIndex,
             final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception {
-        final int TEST_TIME_MS = auditRecording ? 60000 : 2000;
+        final int TEST_TIME_MS = auditRecording ? 10000 : 2000;
         doTest(reportName, localRecord, customHandler, periodsPerSecond, markerPeriodsPerSecond,
                 useByteBuffer, blocking, auditRecording, isChannelIndex,
                 TEST_SR, TEST_CONF, TEST_FORMAT, TEST_TIME_MS);
@@ -1099,6 +1099,7 @@
             AudioTimestamp startTs = new AudioTimestamp();
             assertEquals(AudioRecord.ERROR_INVALID_OPERATION,
                     record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC));
+            assertEquals("invalid getTimestamp doesn't affect nanoTime", 0, startTs.nanoTime);
 
             listener.start(TEST_SR);
             record.startRecording();
@@ -1118,17 +1119,19 @@
             final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels;
             // TODO: verify behavior when buffer size is not a multiple of frame size.
 
-            int startTimeAtFrame = 0;
             int samplesRead = 0;
+            // abstract out the buffer type used with lambda.
+            final byte[] byteData = new byte[BUFFER_SAMPLES];
+            final short[] shortData = new short[BUFFER_SAMPLES];
+            final float[] floatData = new float[BUFFER_SAMPLES];
+            final ByteBuffer byteBuffer =
+                    ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
+            BiFunction<Integer, Boolean, Integer> reader = null;
+
+            // depending on the options, create a lambda to read data.
             if (useByteBuffer) {
-                ByteBuffer byteBuffer =
-                        ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample);
-                while (samplesRead < targetSamples) {
-                    // the first time through, we read a single frame.
-                    // this sets the recording anchor position.
-                    int amount = samplesRead == 0 ? numChannels :
-                        Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                    amount *= bytesPerSample;    // in bytes
+                reader = (samples, blockForData) -> {
+                    final int amount = samples * bytesPerSample;    // in bytes
                     // read always places data at the start of the byte buffer with
                     // position and limit are ignored.  test this by setting
                     // position and limit to arbitrary values here.
@@ -1136,112 +1139,58 @@
                     final int lastLimit = 13;
                     byteBuffer.position(lastPosition);
                     byteBuffer.limit(lastLimit);
-                    int ret = blocking ? record.read(byteBuffer, amount) :
-                        record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
-                    // so long as amount requested in bytes is a multiple of the frame size
-                    // we expect the byte buffer request to be filled.  Caution: the
-                    // byte buffer data will be in native endian order, not Java order.
-                    if (blocking) {
-                        assertEquals(amount, ret);
-                    } else {
-                        assertTrue("0 <= " + ret + " <= " + amount,
-                                0 <= ret && ret <= amount);
-                    }
-                    // position, limit are not changed by read().
-                    assertEquals(lastPosition, byteBuffer.position());
-                    assertEquals(lastLimit, byteBuffer.limit());
-                    if (samplesRead == 0 && ret > 0) {
-                        firstSampleTime = System.currentTimeMillis();
-                    }
-                    samplesRead += ret / bytesPerSample;
-                    if (startTimeAtFrame == 0 && ret > 0 &&
-                            record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                            AudioRecord.SUCCESS) {
-                        startTimeAtFrame = samplesRead / numChannels;
-                    }
-                }
+                    final int ret = blockForData ? record.read(byteBuffer, amount) :
+                            record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING);
+                    return ret / bytesPerSample;
+                };
             } else {
                 switch (TEST_FORMAT) {
-                case AudioFormat.ENCODING_PCM_8BIT: {
-                    // For 8 bit data, use bytes
-                    byte[] byteData = new byte[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = blocking ? record.read(byteData, 0, amount) :
-                            record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
-                case AudioFormat.ENCODING_PCM_16BIT: {
-                    // For 16 bit data, use shorts
-                    short[] shortData = new short[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = blocking ? record.read(shortData, 0, amount) :
-                            record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
-                case AudioFormat.ENCODING_PCM_FLOAT: {
-                    float[] floatData = new float[BUFFER_SAMPLES];
-                    while (samplesRead < targetSamples) {
-                        // the first time through, we read a single frame.
-                        // this sets the recording anchor position.
-                        int amount = samplesRead == 0 ? numChannels :
-                            Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
-                        int ret = record.read(floatData, 0, amount, blocking ?
-                                AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING);
-                        if (blocking) {
-                            assertEquals(amount, ret);
-                        } else {
-                            assertTrue("0 <= " + ret + " <= " + amount,
-                                    0 <= ret && ret <= amount);
-                        }
-                        if (samplesRead == 0 && ret > 0) {
-                            firstSampleTime = System.currentTimeMillis();
-                        }
-                        samplesRead += ret;
-                        if (startTimeAtFrame == 0 && ret > 0 &&
-                                record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC) ==
-                                AudioRecord.SUCCESS) {
-                            startTimeAtFrame = samplesRead / numChannels;
-                        }
-                    }
-                } break;
+                    case AudioFormat.ENCODING_PCM_8BIT:
+                        reader = (samples, blockForData) -> {
+                            return blockForData ? record.read(byteData, 0, samples) :
+                                    record.read(byteData, 0, samples,
+                                            AudioRecord.READ_NON_BLOCKING);
+                        };
+                        break;
+                    case AudioFormat.ENCODING_PCM_16BIT:
+                        reader = (samples, blockForData) -> {
+                            return blockForData ? record.read(shortData, 0, samples) :
+                                    record.read(shortData, 0, samples,
+                                            AudioRecord.READ_NON_BLOCKING);
+                        };
+                        break;
+                    case AudioFormat.ENCODING_PCM_FLOAT:
+                        reader = (samples, blockForData) -> {
+                            return record.read(floatData, 0, samples,
+                                    blockForData ? AudioRecord.READ_BLOCKING
+                                            : AudioRecord.READ_NON_BLOCKING);
+                        };
+                        break;
+                }
+            }
+
+            while (samplesRead < targetSamples) {
+                // the first time through, we read a single frame.
+                // this sets the recording anchor position.
+                final int amount = samplesRead == 0 ? numChannels :
+                    Math.min(BUFFER_SAMPLES, targetSamples - samplesRead);
+                final int ret = reader.apply(amount, blocking);
+                if (blocking) {
+                    assertEquals("blocking reads should return amount requested", amount, ret);
+                } else {
+                    assertTrue("non-blocking reads should return amount in range: " +
+                            "0 <= " + ret + " <= " + amount,
+                            0 <= ret && ret <= amount);
+                }
+                if (samplesRead == 0 && ret > 0) {
+                    firstSampleTime = System.currentTimeMillis();
+                }
+                samplesRead += ret;
+                if (startTs.nanoTime == 0 && ret > 0 &&
+                        record.getTimestamp(startTs, AudioTimestamp.TIMEBASE_MONOTONIC)
+                                == AudioRecord.SUCCESS) {
+                    assertTrue("expecting valid timestamp with nonzero nanoTime",
+                            startTs.nanoTime > 0);
                 }
             }
 
@@ -1259,11 +1208,17 @@
                 Log.w(TAG, "cold input start time too long "
                         + coldInputStartTime + " > 100ms");
             }
-            assertTrue(coldInputStartTime < 5000); // must start within 5 seconds.
+
+            final int COLD_INPUT_START_TIME_LIMIT_MS = 5000;
+            assertTrue("track must start within " + COLD_INPUT_START_TIME_LIMIT_MS + " millis",
+                    coldInputStartTime < COLD_INPUT_START_TIME_LIMIT_MS);
 
             // Verify recording completes within 50 ms of expected test time (typical 20ms)
-            assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ?
-                (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400));
+            final int RECORDING_TIME_TOLERANCE_MS = auditRecording ?
+                (isLowLatencyDevice() ? 1000 : 2000) : (isLowLatencyDevice() ? 50 : 400);
+            assertEquals("recording must complete within " + RECORDING_TIME_TOLERANCE_MS
+                    + " of expected test time",
+                    TEST_TIME_MS, endTime - firstSampleTime, RECORDING_TIME_TOLERANCE_MS);
 
             // Even though we've read all the frames we want, the events may not be sent to
             // the listeners (events are handled through a separate internal callback thread).
@@ -1272,7 +1227,8 @@
 
             stopRequestTime = System.currentTimeMillis();
             record.stop();
-            assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
+            assertEquals("state should be RECORDSTATE_STOPPED after stop()",
+                    AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
 
             stopTime = System.currentTimeMillis();
 
@@ -1290,10 +1246,12 @@
 
             // get stop timestamp
             AudioTimestamp stopTs = new AudioTimestamp();
-            assertEquals(AudioRecord.SUCCESS,
+            assertEquals("should successfully get timestamp after stop",
+                    AudioRecord.SUCCESS,
                     record.getTimestamp(stopTs, AudioTimestamp.TIMEBASE_MONOTONIC));
             AudioTimestamp stopTsBoot = new AudioTimestamp();
-            assertEquals(AudioRecord.SUCCESS,
+            assertEquals("should successfully get boottime timestamp after stop",
+                    AudioRecord.SUCCESS,
                     record.getTimestamp(stopTsBoot, AudioTimestamp.TIMEBASE_BOOTTIME));
 
             // printTimestamp("startTs", startTs);
@@ -1303,13 +1261,16 @@
             // Log.d(TAG, "time Boottime " + SystemClock.elapsedRealtimeNanos());
 
             // stop should not reset timestamps
-            assertTrue(stopTs.framePosition >= targetFrames);
-            assertEquals(stopTs.framePosition, stopTsBoot.framePosition);
-            assertTrue(stopTs.nanoTime > 0);
+            assertTrue("stop timestamp position should be no less than frames read",
+                    stopTs.framePosition >= targetFrames);
+            assertEquals("stop timestamp position should be same "
+                    + "between monotonic and boot timestamps",
+                    stopTs.framePosition, stopTsBoot.framePosition);
+            assertTrue("stop timestamp nanoTime must be set", stopTs.nanoTime > 0);
 
             // timestamps follow a different path than data, so it is conceivable
             // that first data arrives before the first timestamp is ready.
-            assertTrue(startTimeAtFrame > 0); // we read a start timestamp
+            assertTrue("no start timestamp read", startTs.nanoTime > 0);
 
             verifyContinuousTimestamps(startTs, stopTs, TEST_SR);
 
@@ -1324,9 +1285,7 @@
             // resource needed for other tests.
             record.release();
         }
-        if (auditRecording) { // don't check timing if auditing (messes up timing)
-            return;
-        }
+
         final int markerPeriods = markerPeriodsPerSecond * TEST_TIME_MS / 1000;
         final int updatePeriods = periodsPerSecond * TEST_TIME_MS / 1000;
         final int markerPeriodsMax =
@@ -1647,6 +1606,124 @@
         return;
     }
 
+    /**
+     * Test AudioRecord Builder error handling.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testAudioRecordBuilderError() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+
+        final AudioRecord[] audioRecord = new AudioRecord[1]; // pointer to AudioRecord.
+        final int BIGNUM = Integer.MAX_VALUE; // large value that should be invalid.
+        final int INVALID_SESSION_ID = 1024;  // can never occur (wrong type in 3 lsbs)
+        final int INVALID_CHANNEL_MASK = -1;
+
+        try {
+            // NOTE:
+            // AudioFormat tested in AudioFormatTest#testAudioFormatBuilderError.
+
+            // We must be able to create the AudioRecord.
+            audioRecord[0] = new AudioRecord.Builder().build();
+            audioRecord[0].release();
+
+            // Out of bounds buffer size.  A large size will fail in AudioRecord creation.
+            assertThrows(UnsupportedOperationException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setBufferSizeInBytes(BIGNUM)
+                        .build();
+            });
+
+            // 0 and negative buffer size throw IllegalArgumentException
+            for (int bufferSize : new int[] {-BIGNUM, -1, 0}) {
+                assertThrows(IllegalArgumentException.class, () -> {
+                    audioRecord[0] = new AudioRecord.Builder()
+                            .setBufferSizeInBytes(bufferSize)
+                            .build();
+                });
+            }
+
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioSource(BIGNUM)
+                        .build();
+            });
+
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioSource(-2)
+                        .build();
+            });
+
+            // Invalid session id that is positive.
+            // (logcat error message vague)
+            assertThrows(UnsupportedOperationException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setSessionId(INVALID_SESSION_ID)
+                        .build();
+            });
+
+            // Specialty AudioRecord tests
+            assertThrows(NullPointerException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioPlaybackCaptureConfig(null)
+                        .build();
+            });
+
+            assertThrows(NullPointerException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setContext(null)
+                        .build();
+            });
+
+            // Bad audio encoding DRA expected unsupported.
+            try {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
+                                .setEncoding(AudioFormat.ENCODING_DRA)
+                                .build())
+                        .build();
+                // Don't throw an exception, maybe it is supported somehow, but warn.
+                Log.w(TAG, "ENCODING_DRA is expected to be unsupported");
+                audioRecord[0].release();
+                audioRecord[0] = null;
+            } catch (UnsupportedOperationException e) {
+                ; // OK expected
+            }
+
+            // Sample rate out of bounds.
+            // System levels caught on AudioFormat.
+            assertThrows(IllegalArgumentException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setSampleRate(BIGNUM)
+                                .build())
+                        .build();
+            });
+
+            // Invalid channel mask
+            // This is a UOE for AudioRecord vs IAE for AudioTrack.
+            assertThrows(UnsupportedOperationException.class, () -> {
+                audioRecord[0] = new AudioRecord.Builder()
+                        .setAudioFormat(new AudioFormat.Builder()
+                                .setChannelMask(INVALID_CHANNEL_MASK)
+                                .build())
+                        .build();
+            });
+        } finally {
+            // Did we successfully complete for some reason but did not
+            // release?
+            if (audioRecord[0] != null) {
+                audioRecord[0].release();
+                audioRecord[0] = null;
+            }
+        }
+    }
+
     @Test
     public void testPrivacySensitiveBuilder() throws Exception {
         if (!hasMicrophone()) {
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
index bcc48a9..4575fdd 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioTrackTest.java
@@ -1580,19 +1580,27 @@
     }
 
     @Test
-    public void testPlayStaticData() throws Exception {
+    public void testPlayStaticByteArray() throws Exception {
+        doTestPlayStaticData("testPlayStaticByteArray", AudioFormat.ENCODING_PCM_8BIT);
+    }
+
+    @Test
+    public void testPlayStaticShortArray() throws Exception {
+        doTestPlayStaticData("testPlayStaticShortArray", AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    @Test
+    public void testPlayStaticFloatArray() throws Exception {
+        doTestPlayStaticData("testPlayStaticFloatArray", AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    private void doTestPlayStaticData(String testName, int testFormat) throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
             return;
         }
         // constants for test
-        final String TEST_NAME = "testPlayStaticData";
-        final int TEST_FORMAT_ARRAY[] = {  // 6 chirps repeated (TEST_LOOPS+1) times, 3 times
-                AudioFormat.ENCODING_PCM_8BIT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
         final int TEST_SR_ARRAY[] = {
                 12055, // Note multichannel tracks will sound very short at low sample rates
                 48000,
@@ -1613,16 +1621,15 @@
         // 200 ms less for low latency devices.
         final long WAIT_TIME_MS = isLowLatencyDevice() ? WAIT_MSEC : 500;
 
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                            TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_TIME_MS,
-                            TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
+        double frequency = 400; // frequency changes for each test
+        for (int testSampleRate : TEST_SR_ARRAY) {
+            for (int testChannelConfiguration : TEST_CONF_ARRAY) {
+                playOnceStaticData(testName, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                        TEST_LOOPS, testFormat, frequency, testSampleRate,
+                        testChannelConfiguration, WAIT_TIME_MS,
+                        TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS);
 
-                    frequency += 70; // increment test tone frequency
-                }
+                frequency += 70; // increment test tone frequency
             }
         }
     }
@@ -1726,14 +1733,22 @@
     }
 
     @Test
-    public void testPlayStreamData() throws Exception {
+    public void testPlayStreamByteArray() throws Exception {
+        doTestPlayStreamData("testPlayStreamByteArray", AudioFormat.ENCODING_PCM_8BIT);
+    }
+
+    @Test
+    public void testPlayStreamShortArray() throws Exception {
+        doTestPlayStreamData("testPlayStreamShortArray", AudioFormat.ENCODING_PCM_16BIT);
+    }
+
+    @Test
+    public void testPlayStreamFloatArray() throws Exception {
+        doTestPlayStreamData("testPlayStreamFloatArray", AudioFormat.ENCODING_PCM_FLOAT);
+    }
+
+    private void doTestPlayStreamData(String testName, int testFormat) throws Exception {
         // constants for test
-        final String TEST_NAME = "testPlayStreamData";
-        final int TEST_FORMAT_ARRAY[] = {  // should hear 40 increasing frequency tones, 3 times
-                AudioFormat.ENCODING_PCM_8BIT,
-                AudioFormat.ENCODING_PCM_16BIT,
-                AudioFormat.ENCODING_PCM_FLOAT,
-        };
         // due to downmixer algorithmic latency, source channels greater than 2 may
         // sound shorter in duration at 4kHz sampling rate.
         final int TEST_SR_ARRAY[] = {
@@ -1758,15 +1773,14 @@
         final float TEST_SWEEP = 0; // sine wave only
         final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice();
 
-        for (int TEST_FORMAT : TEST_FORMAT_ARRAY) {
-            double frequency = 400; // frequency changes for each test
-            for (int TEST_SR : TEST_SR_ARRAY) {
-                for (int TEST_CONF : TEST_CONF_ARRAY) {
-                    playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
-                            TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF,
-                            WAIT_MSEC, 0 /* mask */);
-                    frequency += 50; // increment test tone frequency
-                }
+        double frequency = 400; // frequency changes for each test
+        for (int testSampleRate : TEST_SR_ARRAY) {
+            for (int testChannelConfiguration : TEST_CONF_ARRAY) {
+                playOnceStreamData(testName, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP,
+                        TEST_IS_LOW_RAM_DEVICE, testFormat, frequency,
+                        testSampleRate, testChannelConfiguration,
+                        WAIT_MSEC, 0 /* mask */);
+                frequency += 50; // increment test tone frequency
             }
         }
     }
@@ -1794,7 +1808,7 @@
         assertEquals(testName, channelCount, format.getChannelCount());
         assertEquals(testName, testFormat, format.getEncoding());
         // duration of test tones
-        final int frames = AudioHelper.frameCountFromMsec(500 /* ms */, format);
+        final int frames = AudioHelper.frameCountFromMsec(300 /* ms */, format);
         final int sourceSamples = channelCount * frames;
         final double frequency = testFrequency / channelCount;
 
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java b/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
new file mode 100644
index 0000000..5c1821b
--- /dev/null
+++ b/tests/tests/media/audio/src/android/media/audio/cts/CallAudioInterceptionTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.media.audio.cts;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioTrack;
+import android.platform.test.annotations.AppModeFull;
+// TODO: b/189472651 uncomment when TM version code is published
+//  import android.os.Build;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+// TODO: b/189472651 uncomment when TM version code is published
+//  import com.android.compatibility.common.util.ApiLevelUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Class to exercise AudioManager.getDevicesForAttributes()
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Instant apps cannot hold android.permission.CALL_AUDIO_INTERCEPTION")
+public class CallAudioInterceptionTest {
+    private static final String TAG = CallAudioInterceptionTest.class.getSimpleName();
+    private static final int SET_MODE_DELAY_MS = 300;
+
+    private AudioManager mAudioManager;
+
+    /** Test setup */
+    @Before
+    public void setUp() throws Exception {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        assumeTrue(context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
+        mAudioManager = context.getSystemService(AudioManager.class);
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.CALL_AUDIO_INTERCEPTION,
+                        Manifest.permission.MODIFY_PHONE_STATE);
+    }
+
+    /** Test teardown */
+    @After
+    public void tearDown() {
+        mAudioManager.setMode(AudioManager.MODE_NORMAL);
+        InstrumentationRegistry.getInstrumentation()
+              .getUiAutomation()
+              .dropShellPermissionIdentity();
+    }
+
+    /**
+     * Test AudioManager.isPstnCallAudioInterceptable() API is implemented
+     */
+    @Test
+    public void testIsIsPstnCallAudioInterceptable() throws Exception {
+        skipIfCallredirectNotAvailable();
+
+        try {
+            boolean result = mAudioManager.isPstnCallAudioInterceptable();
+            Log.i(TAG, "isPstnCallAudioInterceptable: " + result);
+        } catch (Exception e) {
+            fail("isPstnCallAudioInterceptable() exception: " + e);
+        }
+    }
+
+    /**
+     * Test AudioManager.getCallUplinkInjectionAudioTrack() fails when success conditions are
+     * not met.
+     */
+    @Test
+    public void testGetCallUplinkInjectionAudioTrackFail() throws Exception {
+        skipIfCallredirectNotAvailable();
+
+        try {
+            AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(null);
+            fail("getCallUplinkInjectionAudioTrack should throw NullPointerException");
+        } catch (NullPointerException e) {
+        }
+        AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+        setAudioMode(AudioManager.MODE_NORMAL);
+        try {
+            AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+            fail("getCallUplinkInjectionAudioTrack should throw IllegalStateException "
+                    + "in mode NORMAL");
+        } catch (IllegalStateException e) {
+        }
+
+        if (setAudioMode(AudioManager.MODE_IN_CALL)) {
+            try {
+                AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+                if (!mAudioManager.isPstnCallAudioInterceptable()) {
+                    fail("getCallUplinkInjectionAudioTrack should throw"
+                            + "UnsupportedOperationException in mode IN_CALL");
+                }
+            } catch (UnsupportedOperationException e) {
+                if (mAudioManager.isPstnCallAudioInterceptable()) {
+                    fail("getCallUplinkInjectionAudioTrack should not throw"
+                            + "UnsupportedOperationException in mode IN_CALL");
+                }
+            }
+        } else {
+            Log.i(TAG, "Cannot set mode to MODE_IN_CALL");
+        }
+
+        if (setAudioMode(AudioManager.MODE_IN_COMMUNICATION)) {
+            try {
+                format = new AudioFormat.Builder().setSampleRate(96000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+                AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+                fail("getCallUplinkInjectionAudioTrack should throw"
+                        + "UnsupportedOperationException for 96000Hz");
+                format = new AudioFormat.Builder().setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_MP3)
+                        .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+                track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+                fail("getCallUplinkInjectionAudioTrack should throw"
+                        + "UnsupportedOperationException for MP3 encoding");
+                format = new AudioFormat.Builder().setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1).build();
+                track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+                fail("getCallUplinkInjectionAudioTrack should throw"
+                        + "UnsupportedOperationException for 5.1 channels");
+            } catch (UnsupportedOperationException e) {
+            }
+        } else {
+            Log.i(TAG, "Cannot set mode to MODE_IN_COMMUNICATION");
+        }
+    }
+
+    /**
+     * Test AudioManager.getCallUplinkInjectionAudioTrack() succeeds when success conditions are
+     * met.
+     */
+    @Test
+    public void testGetCallUplinkInjectionAudioTrackSuccess() throws Exception {
+        skipIfCallredirectNotAvailable();
+
+        AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_OUT_MONO).build();
+        final int[] TEST_MODES = new int[] { AudioManager.MODE_IN_CALL,
+                AudioManager.MODE_IN_COMMUNICATION,
+                AudioManager.MODE_CALL_REDIRECT,
+                AudioManager.MODE_COMMUNICATION_REDIRECT
+        };
+
+        for (int mode : TEST_MODES) {
+            if (setAudioMode(mode)) {
+                if ((mode != AudioManager.MODE_IN_CALL && mode != AudioManager.MODE_CALL_REDIRECT)
+                        || mAudioManager.isPstnCallAudioInterceptable()) {
+                    try {
+                        Log.i(TAG, "testing mode: " + mode);
+                        AudioTrack track = mAudioManager.getCallUplinkInjectionAudioTrack(format);
+                    } catch (Exception e) {
+                        fail("getCallUplinkInjectionAudioTrack should not throw " + e);
+                    }
+                }
+            } else {
+                Log.i(TAG, "Cannot set mode to: " + mode);
+            }
+        }
+    }
+
+    /**
+     * Test AudioManager.getCallDownlinkExtractionAudioRecord() fails when success conditions are
+     * not met.
+     */
+    @Test
+    public void testGetCallDownlinkExtractionAudioRecordFail() throws Exception {
+        skipIfCallredirectNotAvailable();
+
+        try {
+            AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(null);
+            fail("getCallDownlinkExtractionAudioRecord should throw NullPointerException");
+        } catch (NullPointerException e) {
+        }
+        AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+        setAudioMode(AudioManager.MODE_NORMAL);
+        try {
+            AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+            fail("getCallDownlinkExtractionAudioRecord should throw IllegalStateException "
+                    + "in mode NORMAL");
+        } catch (IllegalStateException e) {
+        }
+
+        if (setAudioMode(AudioManager.MODE_IN_CALL)) {
+            try {
+                AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+                if (!mAudioManager.isPstnCallAudioInterceptable()) {
+                    fail("getCallDownlinkExtractionAudioRecord should throw"
+                            + "UnsupportedOperationException in mode IN_CALL");
+                }
+            } catch (UnsupportedOperationException e) {
+                if (mAudioManager.isPstnCallAudioInterceptable()) {
+                    fail("getCallDownlinkExtractionAudioRecord should not throw"
+                            + " UnsupportedOperationException in mode IN_CALL: " + e);
+                }
+            }
+        } else {
+            Log.i(TAG, "Cannot set mode to MODE_IN_CALL");
+        }
+        if (setAudioMode(AudioManager.MODE_IN_COMMUNICATION)) {
+            try {
+                format = new AudioFormat.Builder().setSampleRate(96000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+                AudioRecord record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+                fail("getCallDownlinkExtractionAudioRecord should throw"
+                        + "UnsupportedOperationException for 96000Hz");
+                format = new AudioFormat.Builder().setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_MP3)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+                record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+                fail("getCallDownlinkExtractionAudioRecord should throw"
+                        + "UnsupportedOperationException for MP3 encoding");
+                format = new AudioFormat.Builder().setSampleRate(16000)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_5POINT1).build();
+                record = mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+                fail("getCallDownlinkExtractionAudioRecord should throw"
+                        + "UnsupportedOperationException for 5.1 channels");
+            } catch (UnsupportedOperationException e) {
+            }
+        } else {
+            Log.i(TAG, "Cannot set mode to MODE_IN_COMMUNICATION");
+        }
+    }
+
+    /**
+     * Test AudioManager.getCallDownlinkExtractionAudioRecord() succeeds when success conditions are
+     * met.
+     */
+    @Test
+    public void testGetCallDownlinkExtractionAudioRecordSuccess() throws Exception {
+        skipIfCallredirectNotAvailable();
+
+        AudioFormat format = new AudioFormat.Builder().setSampleRate(16000)
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build();
+        final int[] TEST_MODES = new int[] { AudioManager.MODE_IN_CALL,
+                AudioManager.MODE_IN_COMMUNICATION,
+                AudioManager.MODE_CALL_REDIRECT,
+                AudioManager.MODE_COMMUNICATION_REDIRECT
+        };
+
+        for (int mode : TEST_MODES) {
+            if (setAudioMode(mode)) {
+                if ((mode != AudioManager.MODE_IN_CALL && mode != AudioManager.MODE_CALL_REDIRECT)
+                        || mAudioManager.isPstnCallAudioInterceptable()) {
+                    try {
+                        AudioRecord record =
+                                mAudioManager.getCallDownlinkExtractionAudioRecord(format);
+                    } catch (Exception e) {
+                        fail("getCallDownlinkExtractionAudioRecord should not throw " + e);
+                    }
+                }
+            } else {
+                Log.i(TAG, "Cannot set mode to: " + mode);
+            }
+        }
+    }
+
+    private boolean setAudioMode(int mode) {
+        mAudioManager.setMode(mode);
+        try {
+            Thread.sleep(SET_MODE_DELAY_MS);
+        } catch (InterruptedException e) {
+
+        }
+        return mAudioManager.getMode() == mode;
+    }
+
+    private void skipIfCallredirectNotAvailable() {
+// TODO: b/189472651 uncomment when TM version code is published
+//        assumeTrue(" Call redirection not available",
+//                ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU));
+    }
+}
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
index 95b1b78..26457d6 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/MidiSoloTest.java
@@ -16,25 +16,20 @@
 
 package android.media.audio.cts;
 
-import android.platform.test.annotations.AppModeFull;
-import java.io.IOException;
-
-
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.media.midi.MidiDevice;
-import android.media.midi.MidiDevice.MidiConnection;
 import android.media.midi.MidiDeviceInfo;
-import android.media.midi.MidiDeviceStatus;
-import android.media.midi.MidiInputPort;
 import android.media.midi.MidiManager;
 import android.media.midi.MidiReceiver;
-import android.media.midi.MidiSender;
 import android.os.Handler;
 import android.os.Looper;
+import android.platform.test.annotations.AppModeFull;
 
 import com.android.compatibility.common.util.CtsAndroidTestCase;
 
+import java.io.IOException;
+import java.util.Collection;
+
 /**
  * Test MIDI when there may be no MIDI devices available. There is not much we
  * can test without a device.
@@ -96,12 +91,22 @@
 
         MidiDeviceInfo[] infos = midiManager.getDevices();
         assertTrue("Device list was null.", infos != null);
+        Collection<MidiDeviceInfo> legacyDeviceInfos = midiManager.getDevicesForTransport(
+                MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+        assertTrue("Legacy Device list was null.", legacyDeviceInfos != null);
+        Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport(
+                MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+        assertTrue("Universal Device list was null.", universalDeviceInfos != null);
 
         MidiManager.DeviceCallback callback = new MidiManager.DeviceCallback();
 
         // These should not crash.
         midiManager.unregisterDeviceCallback(callback);
         midiManager.registerDeviceCallback(callback, null);
+        midiManager.registerDeviceCallbackForTransport(callback, null,
+                MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+        midiManager.registerDeviceCallbackForTransport(callback, null,
+                MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
         midiManager.unregisterDeviceCallback(callback);
         midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
         midiManager.registerDeviceCallback(callback, new Handler(Looper.getMainLooper()));
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
index 3c373de..1408275 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/VolumeShaperTest.java
@@ -16,6 +16,9 @@
 
 package android.media.audio.cts;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityManager;
@@ -34,15 +37,19 @@
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SmallTest;
 
-import com.android.compatibility.common.util.CtsAndroidTestCase;
+import org.junit.Test;
+import org.junit.runner.RunWith;
 
-import java.lang.AutoCloseable;
 import java.util.Arrays;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 /**
  * VolumeShaperTest is automated using VolumeShaper.getVolume() to verify that a ramp
  * or a duck is at the expected volume level. Listening to some tests is also possible,
@@ -53,7 +60,8 @@
  * adb logcat | grep VolumeShaperTest
  */
 @AppModeFull(reason = "TODO: evaluate and port to instant")
-public class VolumeShaperTest extends CtsAndroidTestCase {
+@RunWith(JUnitParamsRunner.class)
+public class VolumeShaperTest {
     private static final String TAG = "VolumeShaperTest";
 
     // ramp or duck time (duration) used in tests.
@@ -150,6 +158,13 @@
                 .setInterpolatorType(VolumeShaper.Configuration.INTERPOLATOR_TYPE_CUBIC)
                 .build();
 
+    private static final VolumeShaper.Configuration[] MONOTONIC_RAMPS = {
+            MONOTONIC_TEST,
+            CUBIC_RAMP,
+            SCURVE_RAMP,
+            SINE_RAMP,
+    };
+
     private static final VolumeShaper.Operation[] ALL_STANDARD_OPERATIONS = {
         VolumeShaper.Operation.PLAY,
         VolumeShaper.Operation.REVERSE,
@@ -343,6 +358,7 @@
     }
 
     @SmallTest
+    @Test
     public void testVolumeShaperConfigurationBuilder() throws Exception {
         final String TEST_NAME = "testVolumeShaperConfigurationBuilder";
 
@@ -500,6 +516,7 @@
     } // testVolumeShaperConfigurationBuilder
 
     @SmallTest
+    @Test
     public void testVolumeShaperConfigurationParcelable() throws Exception {
         final String TEST_NAME = "testVolumeShaperConfigurationParcelable";
 
@@ -525,6 +542,7 @@
     } // testVolumeShaperConfigurationParcelable
 
     @SmallTest
+    @Test
     public void testVolumeShaperOperationParcelable() throws Exception {
         final String TEST_NAME = "testVolumeShaperOperationParcelable";
 
@@ -553,6 +571,7 @@
     // to crash due to memory or performance issues.  Typically around 16 app based
     // shapers are allowed by the audio server.
     @SmallTest
+    @Test
     public void testMaximumShapers() {
         final String TEST_NAME = "testMaximumShapers";
         if (!hasAudioOutput()) {
@@ -582,60 +601,122 @@
         }
     } // testMaximumShapers
 
-    @LargeTest
-    public void testPlayerDuck() throws Exception {
-        final String TEST_NAME = "testPlayerDuck";
+    @Test
+    public void testDuckAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestDuckPlayer("testDuckAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testDuckMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestDuckPlayer("testDuckMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testDuckMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestDuckPlayer("testDuckMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestDuckPlayer(String testName, Player player) throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
             return;
         }
 
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            Log.d(TAG, testName + " starting");
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
 
-                Log.d(TAG, testName + " starting");
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                runDuckTest(testName, volumeShaper);
-                runCloseTest(testName, volumeShaper);
-            }
+            runDuckTest(testName, volumeShaper);
+            runCloseTest(testName, volumeShaper);
         }
-    } // testPlayerDuck
+    }
 
-    @LargeTest
-    public void testPlayerRamp() throws Exception {
-        final String TEST_NAME = "testPlayerRamp";
+    private VolumeShaper.Configuration[] getAllStandardRamps() {
+        return ALL_STANDARD_RAMPS;
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampAudioTrack(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestRampPlayer("testRampAudioTrack", player, configuration);
+        }
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampMediaPlayerNonOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestRampPlayer("testRampMediaPlayerNonOffloaded", player, configuration);
+        }
+    }
+
+    // Parameters are indices to ALL_STANDARD_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getAllStandardRamps")
+    public void testRampMediaPlayerOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestRampPlayer("testRampMediaPlayerOffloaded", player, configuration);
+        }
+    }
+
+    private void runTestRampPlayer(
+            String testName, Player player, VolumeShaper.Configuration configuration)
+            throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
             return;
         }
 
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            Log.d(TAG, testName + " starting");
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
 
-                Log.d(TAG, testName + " starting");
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
-
-                runRampTest(testName, volumeShaper);
-                runCloseTest(testName, volumeShaper);
-            }
+            runRampTest(testName, volumeShaper, configuration);
+            runCloseTest(testName, volumeShaper);
         }
-    } // testPlayerRamp
+    }
 
     @FlakyTest
-    @LargeTest
-    public void testPlayerCornerCase() throws Exception {
-        final String TEST_NAME = "testPlayerCornerCase";
+    @Test
+    public void testCornerCaseAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestCornerCasePlayer("testCornerCaseAudioTrack", player);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCornerCaseMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestCornerCasePlayer("testCornerCaseMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @FlakyTest
+    @Test
+    public void testCornerCaseMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestCornerCasePlayer("testCornerCaseMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestCornerCasePlayer(String testName, Player player) throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
@@ -643,48 +724,40 @@
         }
 
         final VolumeShaper.Configuration config = LINEAR_RAMP;
+        VolumeShaper volumeShaper = null;
+        try {
+            volumeShaper = player.createVolumeShaper(config);
 
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            Player player = null;
-            VolumeShaper volumeShaper = null;
-            try {
-                player = createPlayer(p);
-                volumeShaper = player.createVolumeShaper(config);
-                final String testName = TEST_NAME + " " + player.name();
+            runStartIdleTest(testName, volumeShaper, player);
+            runRampCornerCaseTest(testName, volumeShaper, config);
+            runCloseTest(testName, volumeShaper);
 
-                runStartIdleTest(testName, volumeShaper, player);
-                runRampCornerCaseTest(testName, volumeShaper, config);
-                runCloseTest(testName, volumeShaper);
+            Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
+            volumeShaper = player.createVolumeShaper(config);
+            player.pause();
+            Thread.sleep(100 /* millis */);
+            runStartIdleTest(testName, volumeShaper, player);
 
-                Log.d(TAG, testName + " recreating VolumeShaper and repeating with pause");
-                volumeShaper = player.createVolumeShaper(config);
-                player.pause();
-                Thread.sleep(100 /* millis */);
-                runStartIdleTest(testName, volumeShaper, player);
+            // volumeShaper not explicitly closed, will close upon finalize or player close.
+            Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
+            volumeShaper = player.createVolumeShaper(config);
+            player.stop();
+            Thread.sleep(100 /* millis */);
+            runStartIdleTest(testName, volumeShaper, player);
 
-                // volumeShaper not explicitly closed, will close upon finalize or player close.
-                Log.d(TAG, testName + " recreating VolumeShaper and repeating with stop");
-                volumeShaper = player.createVolumeShaper(config);
-                player.stop();
-                Thread.sleep(100 /* millis */);
-                runStartIdleTest(testName, volumeShaper, player);
-
-                Log.d(TAG, testName + " closing Player before VolumeShaper");
-                player.close();
-                runCloseTest2(testName, volumeShaper);
-            } finally {
-                if (volumeShaper != null) {
-                    volumeShaper.close();
-                }
-                if (player != null) {
-                    player.close();
-                }
+            Log.d(TAG, testName + " closing Player before VolumeShaper");
+            player.close();
+            runCloseTest2(testName, volumeShaper);
+        } finally {
+            if (volumeShaper != null) {
+                volumeShaper.close();
             }
         }
-    } // testPlayerCornerCase
+    } // runTestCornerCasePlayer
 
     @FlakyTest
     @LargeTest
+    @Test
     public void testPlayerCornerCase2() throws Exception {
         final String TEST_NAME = "testPlayerCornerCase2";
         if (!hasAudioOutput()) {
@@ -728,6 +801,7 @@
 
     @FlakyTest
     @LargeTest
+    @Test
     public void testPlayerJoin() throws Exception {
         final String TEST_NAME = "testPlayerJoin";
         if (!hasAudioOutput()) {
@@ -747,7 +821,7 @@
 
                 Log.d(TAG, " we join several LINEAR_RAMPS together "
                         + " this effectively is one LINEAR_RAMP (volume increasing).");
-                final long durationMs = 10000;
+                final long durationMs = 5000;
                 final long incrementMs = 1000;
                 for (long i = 0; i < durationMs; i += incrementMs) {
                     Log.d(TAG, testName + " Play - join " + i);
@@ -764,133 +838,174 @@
         }
     } // testPlayerJoin
 
-    @LargeTest
-    public void testPlayerCubicMonotonic() throws Exception {
-        final String TEST_NAME = "testPlayerCubicMonotonic";
+    private VolumeShaper.Configuration[] getMonotonicRamps() {
+        return MONOTONIC_RAMPS;
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicAudioTrack(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicAudioTrack", player, configuration);
+        }
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicMediaPlayerNonOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicMediaPlayerNonOffloaded", player, configuration);
+        }
+    }
+
+    // Parameters are indices to MONOTONIC_RAMPS configuration array.
+    @Test
+    @Parameters(method = "getMonotonicRamps")
+    public void testCubicMonotonicMediaPlayerOffloaded(
+            VolumeShaper.Configuration configuration) throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestCubicMonotonicPlayer(
+                    "testCubicMonotonicMediaPlayerOffloaded", player, configuration);
+        }
+    }
+
+    private void runTestCubicMonotonicPlayer(
+            String testName, Player player, VolumeShaper.Configuration configuration)
+            throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
             return;
         }
 
-        final VolumeShaper.Configuration configurations[] =
-                new VolumeShaper.Configuration[] {
-                MONOTONIC_TEST,
-                CUBIC_RAMP,
-                SCURVE_RAMP,
-                SINE_RAMP,
-        };
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            volumeShaper.apply(VolumeShaper.Operation.PLAY);
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
 
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-                volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
+            // test configurations known monotonic
+            Log.d(TAG, testName + " starting test");
 
-                for (VolumeShaper.Configuration configuration : configurations) {
-                    // test configurations known monotonic
-                    Log.d(TAG, testName + " starting test");
+            float lastVolume = 0;
+            final long incrementMs = 100;
 
-                    float lastVolume = 0;
-                    final long incrementMs = 100;
-
-                    volumeShaper.replace(configuration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should increase "
-                                + volume + " >= " + lastVolume,
-                                (volume >= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 1.f, but is " + lastVolume,
-                            1.f, lastVolume, VOLUME_TOLERANCE);
-
-                    Log.d(TAG, "invert");
-                    // invert
-                    VolumeShaper.Configuration newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .invertVolumes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should decrease "
-                                + volume + " <= " + lastVolume,
-                                (volume <= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 0.f, but is " + lastVolume,
-                            0.f, lastVolume, VOLUME_TOLERANCE);
-
-                    // invert + reflect
-                    Log.d(TAG, "invert and reflect");
-                    newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .invertVolumes()
-                    .reflectTimes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should increase "
-                                + volume + " >= " + lastVolume,
-                                (volume >= lastVolume - VOLUME_TOLERANCE));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 1.f, but is " + lastVolume,
-                            1.f, lastVolume, VOLUME_TOLERANCE);
-
-                    // reflect
-                    Log.d(TAG, "reflect");
-                    newConfiguration =
-                            new VolumeShaper.Configuration.Builder(configuration)
-                    .reflectTimes()
-                    .build();
-                    volumeShaper.replace(newConfiguration,
-                            VolumeShaper.Operation.PLAY, true /* join */);
-                    // monotonicity test
-                    for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
-                        final float volume = volumeShaper.getVolume();
-                        assertTrue(testName + " montonic volume should decrease "
-                                + volume + " <= " + lastVolume,
-                                (volume <= lastVolume));
-                        lastVolume = volume;
-                        Thread.sleep(incrementMs);
-                    }
-                    Thread.sleep(WARMUP_TIME_MS);
-                    lastVolume = volumeShaper.getVolume();
-                    assertEquals(testName
-                            + " final monotonic value should be 0.f, but is " + lastVolume,
-                            0.f, lastVolume, VOLUME_TOLERANCE);
-                }
+            volumeShaper.replace(configuration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should increase "
+                        + volume + " >= " + lastVolume,
+                        (volume >= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
             }
-        }
-    } // testPlayerCubicMonotonic
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
 
-    @LargeTest
-    public void testPlayerStepRamp() throws Exception {
-        final String TEST_NAME = "testPlayerStepRamp";
+            Log.d(TAG, "invert");
+            // invert
+            VolumeShaper.Configuration newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                    .invertVolumes()
+                    .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should decrease "
+                        + volume + " <= " + lastVolume,
+                        (volume <= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+
+            // invert + reflect
+            Log.d(TAG, "invert and reflect");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+            .invertVolumes()
+            .reflectTimes()
+            .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should increase "
+                        + volume + " >= " + lastVolume,
+                        (volume >= lastVolume - VOLUME_TOLERANCE));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            // reflect
+            Log.d(TAG, "reflect");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+            .reflectTimes()
+            .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
+            // monotonicity test
+            for (long i = 0; i < RAMP_TIME_MS; i += incrementMs) {
+                final float volume = volumeShaper.getVolume();
+                assertTrue(testName + " montonic volume should decrease "
+                        + volume + " <= " + lastVolume,
+                        (volume <= lastVolume));
+                lastVolume = volume;
+                Thread.sleep(incrementMs);
+            }
+            Thread.sleep(WARMUP_TIME_MS);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final monotonic value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
+        }
+    } // runTestCubicMonotonicPlayer
+
+    @Test
+    public void testStepRampAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestStepRampPlayer("testStepRampAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testStepRampMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestStepRampPlayer("testStepRampMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testStepRampMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestStepRampPlayer("testStepRampMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestStepRampPlayer(String testName, Player player) throws Exception {
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
@@ -902,121 +1017,137 @@
         // It should suddenly jump to full volume at 1.f (full duration).
         // Note: invertVolumes() and reflectTimes() are not symmetric for STEP interpolation;
         // however, VolumeShaper.Operation.REVERSE will behave symmetrically.
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
-                volumeShaper.apply(VolumeShaper.Operation.PLAY);
-                player.start();
-                Thread.sleep(WARMUP_TIME_MS);
 
-                final VolumeShaper.Configuration configuration = STEP_RAMP;
-                Log.d(TAG, testName + " starting test (sudden jump to full after "
-                        + RAMP_TIME_MS + " milliseconds)");
+        try (VolumeShaper volumeShaper = player.createVolumeShaper(SILENCE)) {
+            volumeShaper.apply(VolumeShaper.Operation.PLAY);
+            player.start();
+            Thread.sleep(WARMUP_TIME_MS);
 
-                volumeShaper.replace(configuration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
+            final VolumeShaper.Configuration configuration = STEP_RAMP;
+            Log.d(TAG, testName + " starting test (sudden jump to full after "
+                    + RAMP_TIME_MS + " milliseconds)");
 
-                Thread.sleep(RAMP_TIME_MS / 2);
-                float lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
+            volumeShaper.replace(configuration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
 
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            float lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
 
-                Log.d(TAG, "invert (sudden jump to silence after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                // invert
-                VolumeShaper.Configuration newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .invertVolumes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
 
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
+            Log.d(TAG, "invert (sudden jump to silence after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            // invert
+            VolumeShaper.Configuration newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .invertVolumes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
 
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
 
-                // invert + reflect
-                Log.d(TAG, "invert and reflect (sudden jump to full after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .invertVolumes()
-                            .reflectTimes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
 
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
+            // invert + reflect
+            Log.d(TAG, "invert and reflect (sudden jump to full after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .invertVolumes()
+                        .reflectTimes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
 
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
 
-                // reflect
-                Log.d(TAG, "reflect (sudden jump to silence after "
-                        + RAMP_TIME_MS + " milliseconds)");
-                newConfiguration =
-                        new VolumeShaper.Configuration.Builder(configuration)
-                            .reflectTimes()
-                            .build();
-                volumeShaper.replace(newConfiguration,
-                        VolumeShaper.Operation.PLAY, true /* join */);
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
 
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
+            // reflect
+            Log.d(TAG, "reflect (sudden jump to silence after "
+                    + RAMP_TIME_MS + " milliseconds)");
+            newConfiguration =
+                    new VolumeShaper.Configuration.Builder(configuration)
+                        .reflectTimes()
+                        .build();
+            volumeShaper.replace(newConfiguration,
+                    VolumeShaper.Operation.PLAY, true /* join */);
 
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 0.f, but is " + lastVolume,
-                        0.f, lastVolume, VOLUME_TOLERANCE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
 
-                Log.d(TAG, "reverse (immediate jump to full)");
-                volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-                Thread.sleep(RAMP_TIME_MS / 2);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " middle value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 0.f, but is " + lastVolume,
+                    0.f, lastVolume, VOLUME_TOLERANCE);
 
-                Thread.sleep(RAMP_TIME_MS / 2 + 1000);
-                lastVolume = volumeShaper.getVolume();
-                assertEquals(testName
-                        + " final value should be 1.f, but is " + lastVolume,
-                        1.f, lastVolume, VOLUME_TOLERANCE);
-            }
+            Log.d(TAG, "reverse (immediate jump to full)");
+            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+            Thread.sleep(RAMP_TIME_MS / 2);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " middle value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
+
+            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+            lastVolume = volumeShaper.getVolume();
+            assertEquals(testName
+                    + " final value should be 1.f, but is " + lastVolume,
+                    1.f, lastVolume, VOLUME_TOLERANCE);
         }
-    } // testPlayerStepRamp
+    } // runTestStepRampPlayer
 
-    @LargeTest
-    public void testPlayerTwoShapers() throws Exception {
-        final String TEST_NAME = "testPlayerTwoShapers";
+    @Test
+    public void testTwoShapersAudioTrack() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_AUDIO_TRACK)) {
+            runTestTwoShapersPlayer("testTwoShapersAudioTrack", player);
+        }
+    }
+
+    @Test
+    public void testTwoShapersMediaPlayerNonOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_NON_OFFLOADED)) {
+            runTestTwoShapersPlayer("testTwoShapersMediaPlayerNonOffloaded", player);
+        }
+    }
+
+    @Test
+    public void testTwoShapersMediaPlayerOffloaded() throws Exception {
+        try (Player player = createPlayer(PLAYER_TYPE_MEDIA_PLAYER_OFFLOADED)) {
+            runTestTwoShapersPlayer("testTwoShapersMediaPlayerOffloaded", player);
+        }
+    }
+
+    private void runTestTwoShapersPlayer(String testName, Player player) throws Exception {
+
         if (!hasAudioOutput()) {
             Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid "
                     + "audio output HAL");
@@ -1036,72 +1167,69 @@
                     .reflectTimes()
                     .build();
 
-        for (int p = 0; p < PLAYER_TYPES; ++p) {
-            try (   Player player = createPlayer(p);
-                    VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
-                    VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
-                    ) {
-                final String testName = TEST_NAME + " " + player.name();
+        try (
+                VolumeShaper volumeShaperRamp = player.createVolumeShaper(LONG_RAMP);
+                VolumeShaper volumeShaperDuck = player.createVolumeShaper(LONG_DUCK);
+                ) {
+            final float firstVolumeRamp = volumeShaperRamp.getVolume();
+            final float firstVolumeDuck = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " first ramp value should be 0.f, but is " + firstVolumeRamp,
+                    0.f, firstVolumeRamp, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " first duck value should be 1.f, but is " + firstVolumeDuck,
+                    1.f, firstVolumeDuck, VOLUME_TOLERANCE);
+            player.start();
 
-                final float firstVolumeRamp = volumeShaperRamp.getVolume();
-                final float firstVolumeDuck = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " first ramp value should be 0.f, but is " + firstVolumeRamp,
-                        0.f, firstVolumeRamp, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " first duck value should be 1.f, but is " + firstVolumeDuck,
-                        1.f, firstVolumeDuck, VOLUME_TOLERANCE);
-                player.start();
+            Thread.sleep(1000);
 
-                Thread.sleep(1000);
+            final float lastVolumeRamp = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
+                    0.f, lastVolumeRamp, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
+                    1.f, lastVolumeDuck, VOLUME_TOLERANCE);
 
-                final float lastVolumeRamp = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " no-play ramp value should be 0.f, but is " + lastVolumeRamp,
-                        0.f, lastVolumeRamp, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " no-play duck value should be 1.f, but is " + lastVolumeDuck,
-                        1.f, lastVolumeDuck, VOLUME_TOLERANCE);
+            Log.d(TAG, testName + " volume should be silent and start increasing now");
 
-                Log.d(TAG, testName + " volume should be silent and start increasing now");
+            // we actually start now!
+            volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
+            volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
+            Thread.sleep(durationMs / 2);
 
-                // we actually start now!
-                volumeShaperRamp.apply(VolumeShaper.Operation.PLAY);
-                volumeShaperDuck.apply(VolumeShaper.Operation.PLAY);
-                Thread.sleep(durationMs / 2);
+            Log.d(TAG, testName + " volume should be > 0 and about maximum here");
+            final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
+            assertTrue(testName
+                    + " last ramp value should be > 0.f " + lastVolumeRamp2,
+                    lastVolumeRamp2 > 0.f);
+            assertTrue(testName
+                    + " last duck value should be < 1.f " + lastVolumeDuck2,
+                    lastVolumeDuck2 < 1.f);
 
-                Log.d(TAG, testName + " volume should be > 0 and about maximum here");
-                final float lastVolumeRamp2 = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck2 = volumeShaperDuck.getVolume();
-                assertTrue(testName
-                        + " last ramp value should be > 0.f " + lastVolumeRamp2,
-                        lastVolumeRamp2 > 0.f);
-                assertTrue(testName
-                        + " last duck value should be < 1.f " + lastVolumeDuck2,
-                        lastVolumeDuck2 < 1.f);
+            Log.d(TAG, testName + " volume should start decreasing shortly");
+            Thread.sleep(durationMs / 2 + 1000);
 
-                Log.d(TAG, testName + " volume should start decreasing shortly");
-                Thread.sleep(durationMs / 2 + 1000);
+            Log.d(TAG, testName + " volume should be silent now");
+            final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
+            final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
+            assertEquals(testName
+                    + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
+                    1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
+            assertEquals(testName
+                    + " last duck value should be 0.f, but is " + lastVolumeDuck3,
+                    0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
 
-                Log.d(TAG, testName + " volume should be silent now");
-                final float lastVolumeRamp3 = volumeShaperRamp.getVolume();
-                final float lastVolumeDuck3 = volumeShaperDuck.getVolume();
-                assertEquals(testName
-                        + " last ramp value should be 1.f, but is " + lastVolumeRamp3,
-                        1.f, lastVolumeRamp3, VOLUME_TOLERANCE);
-                assertEquals(testName
-                        + " last duck value should be 0.f, but is " + lastVolumeDuck3,
-                        0.f, lastVolumeDuck3, VOLUME_TOLERANCE);
-
-                runCloseTest(testName, volumeShaperRamp);
-                runCloseTest(testName, volumeShaperDuck);
-            }
+            runCloseTest(testName, volumeShaperRamp);
+            runCloseTest(testName, volumeShaperDuck);
         }
-    } // testPlayerTwoShapers
+    } // runTestTwoShapersPlayer
 
     // tests that shaper advances in the presence of pause and stop (time based after start).
     @LargeTest
+    @Test
     public void testPlayerRunDuringPauseStop() throws Exception {
         final String TEST_NAME = "testPlayerRunDuringPauseStop";
         if (!hasAudioOutput()) {
@@ -1156,55 +1284,55 @@
     } // testPlayerRunDuringPauseStop
 
     // Player should be started before calling (as it is not an argument to method).
-    private void runRampTest(String testName, VolumeShaper volumeShaper) throws Exception {
-        for (VolumeShaper.Configuration config : ALL_STANDARD_RAMPS) {
-            // This replaces with play.
-            Log.d(TAG, testName + " Replace + Play (volume should increase)");
-            volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
-            Thread.sleep(RAMP_TIME_MS / 2);
+    private void runRampTest(
+            String testName, VolumeShaper volumeShaper, VolumeShaper.Configuration config)
+            throws Exception {
+        // This replaces with play.
+        Log.d(TAG, testName + " Replace + Play (volume should increase)");
+        volumeShaper.replace(config, VolumeShaper.Operation.PLAY, false /* join */);
+        Thread.sleep(RAMP_TIME_MS / 2);
 
-            // Reverse the direction of the volume shaper curve
-            Log.d(TAG, testName + " Reverse (volume should decrease)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            Thread.sleep(RAMP_TIME_MS / 2 + 1000);
+        // Reverse the direction of the volume shaper curve
+        Log.d(TAG, testName + " Reverse (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS / 2 + 1000);
 
-            Log.d(TAG, testName + " Check Volume (silent)");
-            assertEquals(testName + " volume should be 0.f",
-                    0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+        Log.d(TAG, testName + " Check Volume (silent)");
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
 
-            // Forwards
-            Log.d(TAG, testName + " Play (volume should increase)");
-            volumeShaper.apply(VolumeShaper.Operation.PLAY);
-            Thread.sleep(RAMP_TIME_MS + 1000);
+        // Forwards
+        Log.d(TAG, testName + " Play (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS + 1000);
 
-            Log.d(TAG, testName + " Check Volume (volume at max)");
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+        Log.d(TAG, testName + " Check Volume (volume at max)");
+        assertEquals(testName + " volume should be 1.f",
+                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
 
-            // Reverse
-            Log.d(TAG, testName + " Reverse (volume should decrease)");
-            volumeShaper.apply(VolumeShaper.Operation.REVERSE);
-            Thread.sleep(RAMP_TIME_MS + 1000);
+        // Reverse
+        Log.d(TAG, testName + " Reverse (volume should decrease)");
+        volumeShaper.apply(VolumeShaper.Operation.REVERSE);
+        Thread.sleep(RAMP_TIME_MS + 1000);
 
-            Log.d(TAG, testName + " Check Volume (volume should be silent)");
-            assertEquals(testName + " volume should be 0.f",
-                    0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+        Log.d(TAG, testName + " Check Volume (volume should be silent)");
+        assertEquals(testName + " volume should be 0.f",
+                0.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
 
-            // Forwards
-            Log.d(TAG, testName + " Play (volume should increase)");
-            volumeShaper.apply(VolumeShaper.Operation.PLAY);
-            Thread.sleep(RAMP_TIME_MS + 1000);
+        // Forwards
+        Log.d(TAG, testName + " Play (volume should increase)");
+        volumeShaper.apply(VolumeShaper.Operation.PLAY);
+        Thread.sleep(RAMP_TIME_MS + 1000);
 
-            // Comment out for headset plug/unplug test
-            // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
-            //
+        // Comment out for headset plug/unplug test
+        // Log.d(TAG, testName + " headset check"); Thread.sleep(10000 /* millis */);
+        //
 
-            Log.d(TAG, testName + " Check Volume (volume at max)");
-            assertEquals(testName + " volume should be 1.f",
-                    1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
+        Log.d(TAG, testName + " Check Volume (volume at max)");
+        assertEquals(testName + " volume should be 1.f",
+                1.f, volumeShaper.getVolume(), VOLUME_TOLERANCE);
 
-            Log.d(TAG, testName + " done");
-        }
+        Log.d(TAG, testName + " done");
     } // runRampTest
 
     // Player should be started before calling (as it is not an argument to method).
@@ -1392,4 +1520,8 @@
         assertTrue(testName + " volume should be greater than 0.f",
                 volumeShaper.getVolume() > 0.f);
     } // runStartSyncTest
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getTargetContext();
+    }
 }
diff --git a/tests/tests/media/common/src/android/media/cts/AudioHelper.java b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
index 995acda..a5f61fe 100644
--- a/tests/tests/media/common/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/common/src/android/media/cts/AudioHelper.java
@@ -533,7 +533,7 @@
                                 .setBufferSizeInBytes(bufferOutSize)
                                 .build();
                 Assert.assertEquals(AudioTrack.STATE_INITIALIZED, mTrack.getState());
-                mPosition = 0;
+                mTrackPosition = 0;
                 mFinishAtMs = 0;
             }
         }
@@ -545,8 +545,9 @@
                     AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
             int samples = super.read(audioData, offsetInBytes, sizeInBytes);
             if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples));
-                mPosition += samples / mTrack.getChannelCount();
+                final int result = mTrack.write(audioData, offsetInBytes, samples,
+                        AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / mTrack.getChannelCount();
             }
             return samples;
         }
@@ -558,9 +559,9 @@
                     AudioFormat.ENCODING_PCM_8BIT, getAudioFormat());
             int samples = super.read(audioData, offsetInBytes, sizeInBytes, readMode);
             if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInBytes, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
+                final int result = mTrack.write(audioData, offsetInBytes, samples,
+                        AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / mTrack.getChannelCount();
             }
             return samples;
         }
@@ -572,8 +573,9 @@
                     AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
             int samples = super.read(audioData, offsetInShorts, sizeInShorts);
             if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples));
-                mPosition += samples / mTrack.getChannelCount();
+                final int result = mTrack.write(audioData, offsetInShorts, samples,
+                        AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / mTrack.getChannelCount();
             }
             return samples;
         }
@@ -585,9 +587,9 @@
                     AudioFormat.ENCODING_PCM_16BIT, getAudioFormat());
             int samples = super.read(audioData, offsetInShorts, sizeInShorts, readMode);
             if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
+                final int result = mTrack.write(audioData, offsetInShorts, samples,
+                        AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / mTrack.getChannelCount();
             }
             return samples;
         }
@@ -599,9 +601,9 @@
                     AudioFormat.ENCODING_PCM_FLOAT, getAudioFormat());
             int samples = super.read(audioData, offsetInFloats, sizeInFloats, readMode);
             if (mTrack != null) {
-                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
-                        AudioTrack.WRITE_BLOCKING));
-                mPosition += samples / mTrack.getChannelCount();
+                final int result = mTrack.write(audioData, offsetInFloats, samples,
+                        AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / mTrack.getChannelCount();
             }
             return samples;
         }
@@ -615,10 +617,9 @@
                 // which does check position and limit.
                 ByteBuffer copy = audioBuffer.duplicate();
                 copy.position(0).limit(bytes);  // read places data at the start of the buffer.
-                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
-                mPosition += bytes /
-                        (mTrack.getChannelCount()
-                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+                final int result =  mTrack.write(copy, bytes, AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / (mTrack.getChannelCount()
+                        * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
             }
             return bytes;
         }
@@ -632,10 +633,9 @@
                 // which does check position and limit.
                 ByteBuffer copy = audioBuffer.duplicate();
                 copy.position(0).limit(bytes);  // read places data at the start of the buffer.
-                Assert.assertEquals(bytes, mTrack.write(copy, bytes, AudioTrack.WRITE_BLOCKING));
-                mPosition += bytes /
-                        (mTrack.getChannelCount()
-                                * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
+                final int result = mTrack.write(copy, bytes, AudioTrack.WRITE_NON_BLOCKING);
+                mTrackPosition += result / (mTrack.getChannelCount()
+                        * AudioFormat.getBytesPerSample(mTrack.getAudioFormat()));
             }
             return bytes;
         }
@@ -652,11 +652,11 @@
         public void stop() {
             super.stop();
             if (mTrack != null) {
-                if (mPosition > 0) { // stop may be called multiple times.
-                    final int remainingFrames = mPosition - mTrack.getPlaybackHeadPosition();
+                if (mTrackPosition > 0) { // stop may be called multiple times.
+                    final int remainingFrames = mTrackPosition - mTrack.getPlaybackHeadPosition();
                     mFinishAtMs = System.currentTimeMillis()
                             + remainingFrames * 1000 / mTrack.getSampleRate();
-                    mPosition = 0;
+                    mTrackPosition = 0;
                 }
                 mTrack.stop(); // allows remaining data to play out
             }
@@ -681,7 +681,7 @@
 
         public AudioTrack mTrack;
         private final static String TAG = "AudioRecordAudit";
-        private int mPosition;
+        private int mTrackPosition;
         private long mFinishAtMs;
     }
 }
diff --git a/tests/tests/media/common/src/android/media/cts/CodecState.java b/tests/tests/media/common/src/android/media/cts/CodecState.java
index 1c08259..e117c3b 100644
--- a/tests/tests/media/common/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/common/src/android/media/cts/CodecState.java
@@ -70,6 +70,7 @@
     private long mFirstSampleTimeUs;
     private long mPlaybackStartTimeUs;
     private long mLastPresentTimeUs;
+    private long mOffsetTimestampUs = 0;
     private MediaCodec mCodec;
     private MediaTimeProvider mMediaTimeProvider;
     private MediaExtractor mExtractor;
@@ -194,7 +195,7 @@
     public long getCurrentPositionUs() {
         // Use decoded frame time when available, otherwise default to render time (typically, in
         // tunnel mode).
-        if (mDecodedFramePresentationTimeUs > 0) {
+        if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
             return mDecodedFramePresentationTimeUs;
         } else {
             return mRenderedVideoFramePresentationTimeUs;
@@ -364,7 +365,7 @@
                 sampleTime -= mFirstSampleTimeUs;
             }
 
-            mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime;
+            mLastPresentTimeUs = mPlaybackStartTimeUs + sampleTime + mOffsetTimestampUs;
 
             if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
                 MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
@@ -655,4 +656,25 @@
         }
         return null;
     }
+
+    /**
+     * Seek media extractor to the beginning of the configured track.
+     *
+     * @param shouldContinuePts  a boolean that controls whether timestamps keep increasing
+     */
+    public void seekToBeginning(boolean shouldContinuePts) {
+        mExtractor.seekTo(UNINITIALIZED_TIMESTAMP, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
+        if (shouldContinuePts) {
+            if (mDecodedFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+                mOffsetTimestampUs = mDecodedFramePresentationTimeUs;
+                return;
+            }
+            if (mRenderedVideoFramePresentationTimeUs != UNINITIALIZED_TIMESTAMP) {
+                mOffsetTimestampUs = mRenderedVideoFramePresentationTimeUs;
+                return;
+            }
+        } else {
+            mOffsetTimestampUs = 0;
+        }
+    }
 }
diff --git a/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
index ef371da..a602a13 100644
--- a/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
+++ b/tests/tests/media/common/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -41,9 +41,13 @@
 public class MediaCodecTunneledPlayer implements MediaTimeProvider {
     private static final String TAG = MediaCodecTunneledPlayer.class.getSimpleName();
 
+    /** State the player starts in, before configuration. */
     private static final int STATE_IDLE = 1;
+    /** State of the player during initial configuration. */
     private static final int STATE_PREPARING = 2;
+    /** State of the player during playback. */
     private static final int STATE_PLAYING = 3;
+    /** State of the player when configured but not playing. */
     private static final int STATE_PAUSED = 4;
 
     private Boolean mThreadStarted = false;
@@ -398,12 +402,14 @@
 
     /**
      * Flushes all the video codecs when the player is in stand-by.
+     *
+     * @throws IllegalStateException  if the player is not paused
      */
     public void videoFlush() {
         Log.d(TAG, "videoFlush");
         synchronized (mState) {
-            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
-                return;
+            if (mState != STATE_PAUSED) {
+                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
             }
 
             for (CodecState state : mVideoCodecStates.values()) {
@@ -412,6 +418,24 @@
         }
     }
 
+    /** Seek all video tracks to their very beginning.
+     *
+     * @param  shouldContinuePts      a boolean that controls whether timestamps keep increasing
+     * @throws IllegalStateException  if the player is not paused
+     */
+    public void videoSeekToBeginning(boolean shouldContinuePts) {
+        Log.d(TAG, "videoSeekToBeginning");
+        synchronized (mState) {
+            if (mState != STATE_PAUSED) {
+                throw new IllegalStateException("Expected STATE_PAUSED, got " + mState);
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.seekToBeginning(shouldContinuePts);
+            }
+        }
+    }
+
     /**
      * Enables or disables looping. Should be called after {@link #prepare()}.
      */
diff --git a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
index 1593193..5405b32 100644
--- a/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
+++ b/tests/tests/media/decoder/src/android/media/decoder/cts/DecoderTest.java
@@ -59,7 +59,6 @@
 import android.media.cts.Preconditions;
 import android.media.cts.SdkMediaCodec;
 import android.net.Uri;
-import android.os.ParcelFileDescriptor;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -4348,9 +4347,13 @@
                 mMediaCodecPlayer.getCurrentPosition());
         mMediaCodecPlayer.pause();
         Thread.sleep(50);
-        assertTrue("Video is ahead of audio", mMediaCodecPlayer.getCurrentPosition() <=
-                mMediaCodecPlayer.getAudioTrackPositionUs());
+        final long audioPositionUs = mMediaCodecPlayer.getAudioTrackPositionUs();
+        final long videoPositionUs = mMediaCodecPlayer.getCurrentPosition();
+        assertTrue(String.format("Video pts (%d) is ahead of audio pts (%d)",
+                        videoPositionUs, audioPositionUs),
+                videoPositionUs <= audioPositionUs);
         mMediaCodecPlayer.videoFlush();
+        mMediaCodecPlayer.videoSeekToBeginning(true /* shouldContinuePts */);
         Thread.sleep(50);
         assertEquals("Video frame rendered after flush", CodecState.UNINITIALIZED_TIMESTAMP,
                 mMediaCodecPlayer.getCurrentPosition());
diff --git a/tests/tests/media/drm/Android.bp b/tests/tests/media/drm/Android.bp
index 63facc3a..05545db 100644
--- a/tests/tests/media/drm/Android.bp
+++ b/tests/tests/media/drm/Android.bp
@@ -65,6 +65,7 @@
         "mts-media",
     ],
     host_required: ["cts-dynamic-config"],
-    min_sdk_version: "29",
-    target_sdk_version: "31",
+    // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+    min_sdk_version: "current",
+    target_sdk_version: "current",
 }
diff --git a/tests/tests/media/drm/AndroidTest.xml b/tests/tests/media/drm/AndroidTest.xml
index 5715237..6a4fb73 100644
--- a/tests/tests/media/drm/AndroidTest.xml
+++ b/tests/tests/media/drm/AndroidTest.xml
@@ -19,6 +19,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
         <option name="target" value="host" />
         <option name="config-filename" value="CtsMediaDrmTestCases" />
diff --git a/tests/tests/media/drm/OWNERS b/tests/tests/media/drm/OWNERS
index 4e95edc..09b0051 100644
--- a/tests/tests/media/drm/OWNERS
+++ b/tests/tests/media/drm/OWNERS
@@ -1,10 +1,13 @@
 # Bug component: 1344
 # android-drm-team
+conglin@google.com
 edwinwong@google.com
 fredgc@google.com
 jtinker@google.com
+juce@google.com
 kylealexander@google.com
 mattfedd@google.com
 rfrias@google.com
 robertshih@google.com
 sigquit@google.com
+vickymin@google.com
diff --git a/tests/tests/media/drm/jni/Android.bp b/tests/tests/media/drm/jni/Android.bp
index 0dc55fa..3a755e5 100644
--- a/tests/tests/media/drm/jni/Android.bp
+++ b/tests/tests/media/drm/jni/Android.bp
@@ -42,5 +42,6 @@
     ],
     stl: "libc++_static",
     gtest: false,
-    sdk_version: "29",
+    // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+    sdk_version: "current",
 }
diff --git a/tests/tests/media/drm/jni/native-mediadrm-jni.cpp b/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
index e0433ba..441d17a 100644
--- a/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
+++ b/tests/tests/media/drm/jni/native-mediadrm-jni.cpp
@@ -62,7 +62,10 @@
 
 static const char kFileScheme[] = "file://";
 static constexpr size_t kFileSchemeStrLen = sizeof(kFileScheme) - 1;
-static constexpr size_t kPlayTimeSeconds = 30;
+// Test time must be under 30 seconds to meet CTS quality bar.
+// The first ten seconds in kPlayTimeSeconds plays the clear lead,
+// the next ten seconds verifies encrypted playback.
+static constexpr size_t kPlayTimeSeconds = 20;
 static constexpr size_t kUuidSize = 16;
 
 static const uint8_t kClearKeyUuid[kUuidSize] = {
@@ -103,6 +106,8 @@
     0x72, 0x79, 0x22, 0x7d
 };
 
+static const char kDefaultUrl[] = "https://default.url";
+
 static const size_t kKeyRequestSize = sizeof(kKeyRequestData);
 
 // base 64 encoded JSON response string, must not contain padding character '='
@@ -970,6 +975,87 @@
     return JNI_TRUE;
 }
 
+extern "C" jboolean Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testGetKeyRequestNative(
+    JNIEnv* env, jclass /*clazz*/, jbyteArray uuid, jobject playbackParams) {
+
+    if (NULL == uuid || NULL == playbackParams) {
+        jniThrowException(env, "java/lang/NullPointerException",
+                "null uuid or null playback parameters");
+        return JNI_FALSE;
+    }
+
+    Uuid juuid = jbyteArrayToUuid(env, uuid);
+    if (!isUuidSizeValid(juuid)) {
+        jniThrowExceptionFmt(env, "java/lang/IllegalArgumentException",
+                "invalid UUID size, expected %u bytes", kUuidSize);
+        return JNI_FALSE;
+    }
+
+    PlaybackParams params;
+    initPlaybackParams(env, playbackParams, params);
+
+    AMediaObjects aMediaObjects;
+    media_status_t status = AMEDIA_OK;
+    aMediaObjects.setDrm(AMediaDrm_createByUUID(&juuid[0]));
+    if (NULL == aMediaObjects.getDrm()) {
+        jniThrowException(env, "java/lang/RuntimeException", "null MediaDrm");
+        return JNI_FALSE;
+    }
+
+    AMediaDrmSessionId sessionId;
+    status = AMediaDrm_openSession(aMediaObjects.getDrm(), &sessionId);
+    if (status != AMEDIA_OK) {
+        jniThrowException(env, "java/lang/RuntimeException",
+                "openSession failed");
+        return JNI_FALSE;
+    }
+
+    // Pointer to keyRequest memory, which remains until the next
+    // AMediaDrm_getKeyRequest call or until the drm object is released.
+    const uint8_t* keyRequest;
+    size_t keyRequestSize = 0;
+    std::string errorMessage;
+
+    const char *defaultUrl;
+    AMediaDrmKeyRequestType keyRequestType;
+
+    // The server recognizes "video/mp4" but not "video/avc".
+    status = AMediaDrm_getKeyRequestWithDefaultUrlAndType(aMediaObjects.getDrm(),
+            &sessionId, kClearkeyPssh, sizeof(kClearkeyPssh),
+            "video/mp4" /*mimeType*/, KEY_TYPE_STREAMING,
+            NULL, 0, &keyRequest, &keyRequestSize, &defaultUrl, &keyRequestType);
+
+    if(status != AMEDIA_OK) return JNI_FALSE;
+
+    switch(keyRequestType) {
+        case KEY_REQUEST_TYPE_INITIAL:
+        case KEY_REQUEST_TYPE_RENEWAL:
+        case KEY_REQUEST_TYPE_RELEASE:
+        case KEY_REQUEST_TYPE_NONE:
+        case KEY_REQUEST_TYPE_UPDATE:
+            break;
+        default:
+            errorMessage.assign("keyRequestType returned is [%d], error = %d");
+            AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+            jniThrowExceptionFmt(env, "java/lang/RuntimeException", errorMessage.c_str(), keyRequestType, status);
+            return JNI_FALSE;
+    }
+
+    ALOGD("kDefaultUrl [%s], length %d, defaultUrl [%s], length %d",
+        kDefaultUrl,
+        (int)strlen(kDefaultUrl),
+        defaultUrl,
+        (int)strlen(defaultUrl));
+
+    if (strlen(kDefaultUrl) != strlen(defaultUrl) || strcmp(kDefaultUrl, defaultUrl) != 0) {
+        AMediaDrm_closeSession(aMediaObjects.getDrm(), &sessionId);
+        jniThrowExceptionFmt(env, "java/lang/RuntimeException", "Default Url is not correct [%s], error = %d", defaultUrl, status);
+        return JNI_FALSE;
+    }
+
+    return JNI_TRUE;
+}
+
 static JNINativeMethod gMethods[] = {
     { "isCryptoSchemeSupportedNative", "([B)Z",
             (void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_isCryptoSchemeSupportedNative },
@@ -994,6 +1080,10 @@
 
     { "testFindSessionIdNative", "([B)Z",
             (void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testFindSessionIdNative },
+
+    { "testGetKeyRequestNative",
+            "([BLandroid/media/drm/cts/NativeMediaDrmClearkeyTest$PlaybackParams;)Z",
+            (void *)Java_android_mediadrm_cts_NativeMediaDrmClearkeyTest_testGetKeyRequestNative},
 };
 
 int register_android_mediadrm_cts_NativeMediaDrmClearkeyTest(JNIEnv* env) {
diff --git a/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java b/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
index 5445692..80d6fb8 100644
--- a/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
+++ b/tests/tests/media/drm/src/android/media/drm/cts/MediaPlayerDrmTestBase.java
@@ -137,7 +137,12 @@
 
     private static final String TAG = "MediaPlayerDrmTestBase";
 
-    protected static final int PLAY_TIME_MS = 60 * 1000;
+    // PLAY_TIME_MS dictates the total run time of the playback
+    // tests. To meet the under 30 seconds CTS quality bar,
+    // the first ten seconds plays the clear lead,
+    // the next ten seconds verifies encrypted playback.
+    // This applies to both streaming and offline tests.
+    protected static final int PLAY_TIME_MS = 20 * 1000;
     protected byte[] mKeySetId;
     protected boolean mAudioOnly;
 
diff --git a/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java b/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
index de21b7f..8f6f874 100644
--- a/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
+++ b/tests/tests/media/drm/src/android/media/drm/cts/NativeMediaDrmClearkeyTest.java
@@ -22,9 +22,13 @@
 import android.media.cts.MediaCodecBlockModelHelper;
 import android.media.cts.Utils;
 import android.net.Uri;
+import android.os.Build;
+import android.platform.test.annotations.FlakyTest;
+import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.view.Surface;
+import androidx.test.filters.SdkSuppress;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
 import com.android.compatibility.common.util.MediaUtils;
@@ -127,6 +131,7 @@
         return buffer.array();
     }
 
+    @Presubmit
     public void testIsCryptoSchemeSupported() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -136,16 +141,19 @@
         assertTrue(isCryptoSchemeSupportedNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
     }
 
+    @Presubmit
     public void testIsCryptoSchemeNotSupported() throws Exception {
         assertFalse(isCryptoSchemeSupportedNative(uuidByteArray(BAD_SCHEME_UUID)));
     }
 
+    @Presubmit
     public void testPssh() throws Exception {
         // The test uses a canned PSSH that contains the common box UUID.
         assertTrue(testPsshNative(uuidByteArray(COMMON_PSSH_SCHEME_UUID),
                 Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString()));
     }
 
+    @Presubmit
     public void testQueryKeyStatus() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -154,6 +162,7 @@
         assertTrue(testQueryKeyStatusNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
     }
 
+    @Presubmit
     public void testFindSessionId() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -162,6 +171,7 @@
         assertTrue(testFindSessionIdNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
     }
 
+    @Presubmit
     public void testGetPropertyString() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -176,6 +186,7 @@
         assertEquals("ClearKey CDM", value.toString());
     }
 
+    @Presubmit
     public void testPropertyByteArray() throws Exception {
         if (watchHasNoClearkeySupport()) {
             return;
@@ -184,6 +195,7 @@
         assertTrue(testPropertyByteArrayNative(uuidByteArray(CLEARKEY_SCHEME_UUID)));
     }
 
+    @Presubmit
     public void testUnknownPropertyString() throws Exception {
         StringBuffer value = new StringBuffer();
 
@@ -288,6 +300,9 @@
 
     private static native boolean testQueryKeyStatusNative(final byte[] uuid);
 
+    private static native boolean testGetKeyRequestNative(final byte[] uuid,
+            PlaybackParams params);
+
     public void testClearKeyPlaybackCenc() throws Exception {
         testClearKeyPlayback(
             COMMON_PSSH_SCHEME_UUID,
@@ -297,6 +312,8 @@
             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
     }
 
+    @FlakyTest(bugId = 173646795)
+    @Presubmit
     public void testClearKeyPlaybackCenc2() throws Exception {
         testClearKeyPlayback(
             CLEARKEY_SCHEME_UUID,
@@ -305,5 +322,20 @@
             Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH),
             VIDEO_WIDTH_CENC, VIDEO_HEIGHT_CENC);
     }
+
+    // TODO(b/208938664) Change this sdk version suppression to T once it's defined to number (33).
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S_V2)
+    public void testClearKeyGetKeyRequest() throws Exception {
+        PlaybackParams params = new PlaybackParams();
+        params.surface = mActivity.getSurfaceHolder().getSurface();
+        params.mimeType = ISO_BMFF_VIDEO_MIME_TYPE;
+        params.audioUrl = Uri.parse(Utils.getMediaPath() + CENC_AUDIO_PATH).toString();
+        params.videoUrl = Uri.parse(Utils.getMediaPath() + CENC_CLEARKEY_VIDEO_PATH).toString();
+        boolean status = testGetKeyRequestNative(
+                uuidByteArray(CLEARKEY_SCHEME_UUID),
+                params);
+        assertTrue(status);
+        params.surface.release();
+    }
 }
 
diff --git a/tests/tests/media/muxer/Android.bp b/tests/tests/media/muxer/Android.bp
index 3eb38a6..f252315 100644
--- a/tests/tests/media/muxer/Android.bp
+++ b/tests/tests/media/muxer/Android.bp
@@ -44,8 +44,9 @@
     // include both the 32 and 64 bit versions
     compile_multilib: "both",
     static_libs: [
-        "ctstestrunner-axt",
         "cts-media-common",
+        "ctstestrunner-axt",
+        "exoplayer2-metadataretriever-mediamuxer",
     ],
     jni_libs: [
         "libctsmediamuxertest_jni",
diff --git a/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
index bf69401..6105dda 100644
--- a/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
+++ b/tests/tests/media/muxer/src/android/media/muxer/cts/MediaMuxerTest.java
@@ -16,6 +16,8 @@
 
 package android.media.muxer.cts;
 
+import static org.junit.Assert.assertArrayEquals;
+
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.media.MediaCodec.BufferInfo;
@@ -32,6 +34,13 @@
 
 import com.android.compatibility.common.util.MediaUtils;
 
+import com.google.android.exoplayer2.Format;
+import com.google.android.exoplayer2.MediaItem;
+import com.google.android.exoplayer2.MetadataRetriever;
+import com.google.android.exoplayer2.source.TrackGroupArray;
+import com.google.android.exoplayer2.util.Util;
+import com.google.android.exoplayer2.video.ColorInfo;
+
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.io.IOException;
@@ -39,6 +48,8 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Vector;
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
 import java.util.stream.IntStream;
 
 @AppModeFull(reason = "No interaction with system server")
@@ -336,6 +347,75 @@
         checkTimestampsWithStartOffsets(startOffsetUsVect);
     }
 
+    public void testAdditionOfHdrStaticMetadata() throws Exception {
+        String outputFilePath =
+                File.createTempFile("MediaMuxerTest_testAdditionOfHdrStaticMetadata", ".mp4")
+                        .getAbsolutePath();
+        // HDR static metadata encoding the following information (format defined in CTA-861.3 -
+        // Static Metadata Descriptor, includes descriptor ID):
+        // Mastering display color primaries:
+        //   R: x=0.677980 y=0.321980, G: x=0.245000 y=0.703000, B: x=0.137980 y=0.052000,
+        //   White point: x=0.312680 y=0.328980
+        // Mastering display luminance min: 0.0000 cd/m2, max: 1000 cd/m2
+        // Maximum Content Light Level: 1100 cd/m2
+        // Maximum Frame-Average Light Level: 180 cd/m2
+        byte[] inputHdrStaticMetadata =
+                Util.getBytesFromHexString("006b84e33eda2f4e89f31a280a123d4140e80300004c04b400");
+        Function<MediaFormat, MediaFormat> staticMetadataAdditionFunction =
+                (mediaFormat) -> {
+                    if (!mediaFormat.getString(MediaFormat.KEY_MIME).startsWith("video/")) {
+                        return mediaFormat;
+                    }
+                    MediaFormat result = new MediaFormat(mediaFormat);
+                    result.setByteBuffer(
+                            MediaFormat.KEY_HDR_STATIC_INFO,
+                            ByteBuffer.wrap(inputHdrStaticMetadata));
+                    return result;
+                };
+        try {
+            cloneMediaUsingMuxer(
+                    /* srcMedia= */ "video_h264_main_b_frames.mp4",
+                    outputFilePath,
+                    /* expectedTrackCount= */ 2,
+                    /* degrees= */ 0,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                    staticMetadataAdditionFunction);
+            assertArrayEquals(
+                    inputHdrStaticMetadata, getVideoColorInfo(outputFilePath).hdrStaticInfo);
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
+    public void testAdditionOfInvalidHdrStaticMetadataIsIgnored() throws Exception {
+        String outputFilePath =
+                File.createTempFile(
+                                "MediaMuxerTest_testAdditionOfInvalidHdrStaticMetadataIsIgnored",
+                                ".mp4")
+                        .getAbsolutePath();
+        Function<MediaFormat, MediaFormat> staticMetadataAdditionFunction =
+                (mediaFormat) -> {
+                    MediaFormat result = new MediaFormat(mediaFormat);
+                    // The input static info should be ignored, because its size is invalid (26 vs
+                    // expected 25).
+                    result.setByteBuffer(
+                            MediaFormat.KEY_HDR_STATIC_INFO, ByteBuffer.allocateDirect(26));
+                    return result;
+                };
+        try {
+            cloneMediaUsingMuxer(
+                    /* srcMedia= */ "video_h264_main_b_frames.mp4",
+                    outputFilePath,
+                    /* expectedTrackCount= */ 2,
+                    /* degrees= */ 0,
+                    MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4,
+                    staticMetadataAdditionFunction);
+            assertNull(getVideoColorInfo(outputFilePath));
+        } finally {
+            new File(outputFilePath).delete();
+        }
+    }
+
     /**
      * Test: makes sure if audio/video muxing using MPEG4Writer works with B Frames
      * when video and audio samples start after different times.
@@ -430,8 +510,13 @@
     private void cloneAndVerify(final String srcMedia, String outputMediaFile,
             int expectedTrackCount, int degrees, int fmt) throws IOException {
         try {
-            cloneMediaUsingMuxer(srcMedia, outputMediaFile, expectedTrackCount,
-                    degrees, fmt);
+            cloneMediaUsingMuxer(
+                    srcMedia,
+                    outputMediaFile,
+                    expectedTrackCount,
+                    degrees,
+                    fmt,
+                    Function.identity());
             if (fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4 ||
                     fmt == MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP) {
                 verifyAttributesMatch(srcMedia, outputMediaFile, degrees);
@@ -445,11 +530,14 @@
         }
     }
 
-    /**
-     * Using the MediaMuxer to clone a media file.
-     */
-    private void cloneMediaUsingMuxer(final String  srcMedia, String dstMediaPath,
-            int expectedTrackCount, int degrees, int fmt)
+    /** Using the MediaMuxer to clone a media file. */
+    private void cloneMediaUsingMuxer(
+            final String srcMedia,
+            String dstMediaPath,
+            int expectedTrackCount,
+            int degrees,
+            int fmt,
+            Function<MediaFormat, MediaFormat> muxerInputTrackFormatTransformer)
             throws IOException {
         // Set up MediaExtractor to read from the source.
         AssetFileDescriptor srcFd = getAssetFileDescriptorFor(srcMedia);
@@ -469,7 +557,7 @@
         for (int i = 0; i < trackCount; i++) {
             extractor.selectTrack(i);
             MediaFormat format = extractor.getTrackFormat(i);
-            int dstIndex = muxer.addTrack(format);
+            int dstIndex = muxer.addTrack(muxerInputTrackFormatTransformer.apply(format));
             indexMap.put(i, dstIndex);
         }
 
@@ -594,7 +682,7 @@
         testFd.close();
     }
 
-    private void verifyLocationInFile(String fileName) {
+    private void verifyLocationInFile(String fileName) throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         retriever.setDataSource(fileName);
         String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
@@ -1165,5 +1253,19 @@
             fail("either audio track has not reached its last sample");
         }
     }
+
+    /** Returns the static HDR metadata in the given {@code file}, or null if not present. */
+    private ColorInfo getVideoColorInfo(String path)
+            throws ExecutionException, InterruptedException {
+        TrackGroupArray trackGroupArray =
+                MetadataRetriever.retrieveMetadata(getContext(), MediaItem.fromUri(path)).get();
+        for (int i = 0; i < trackGroupArray.length; i++) {
+            Format format = trackGroupArray.get(i).getFormat(0);
+            if (format.sampleMimeType.startsWith("video/")) {
+                return format.colorInfo;
+            }
+        }
+        return null;
+    }
 }
 
diff --git a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
index b530f66..b46366c 100644
--- a/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
+++ b/tests/tests/media/player/src/android/media/player/cts/MediaPlayerTest.java
@@ -76,6 +76,7 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
@@ -1182,7 +1183,7 @@
         playVideoTest(file, displayWidth, displayHeight);
     }
 
-    private void checkVideoRotationAngle(int angle, String file) {
+    private void checkVideoRotationAngle(int angle, String file) throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         retriever.setDataSource(file);
         String rotation = retriever.extractMetadata(
diff --git a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
index 5049978..ffc2dcf 100644
--- a/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
+++ b/tests/tests/media/recorder/src/android/media/recorder/cts/MediaRecorderTest.java
@@ -514,7 +514,7 @@
         retriever = null;
     }
 
-    private boolean checkLocationInFile(String fileName) {
+    private boolean checkLocationInFile(String fileName) throws IOException {
         MediaMetadataRetriever retriever = new MediaMetadataRetriever();
         retriever.setDataSource(fileName);
         String location = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
diff --git a/tests/tests/media/src/android/media/cts/HeifWriterTest.java b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
index 40d6128..7694d4c 100644
--- a/tests/tests/media/src/android/media/cts/HeifWriterTest.java
+++ b/tests/tests/media/src/android/media/cts/HeifWriterTest.java
@@ -419,7 +419,11 @@
                 }
                 mWidth = mBitmaps[0].getWidth();
                 mHeight = mBitmaps[0].getHeight();
-                retriever.release();
+                try {
+                    retriever.release();
+                } catch (IOException e) {
+                    // Ignoring errors occurred while releasing the MediaMetadataRetriever.
+                }
             }
 
             private void cleanupStaleOutputs() {
diff --git a/tests/tests/media/src/android/media/cts/MediaActivityTest.java b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
index 8cbe255..f38dada 100644
--- a/tests/tests/media/src/android/media/cts/MediaActivityTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaActivityTest.java
@@ -16,17 +16,19 @@
 
 package android.media.cts;
 
-import static junit.framework.Assert.assertEquals;
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assert.fail;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertFalse;
+import static org.junit.Assert.fail;
 
+import android.Manifest;
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
+import android.hardware.hdmi.HdmiControlManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.session.MediaSession;
@@ -84,17 +86,26 @@
     private Map<Integer, Integer> mStreamVolumeMap = new HashMap<>();
     private MediaSession mSession;
 
+    private HdmiControlManager mHdmiControlManager;
+    private int mHdmiEnableStatus;
+
     @Rule
     public ActivityTestRule<MediaSessionTestActivity> mActivityRule =
             new ActivityTestRule<>(MediaSessionTestActivity.class, false, false);
 
     @Before
     public void setUp() throws Exception {
+        getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+            Manifest.permission.HDMI_CEC);
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getContext();
-        mUseFixedVolume = mContext.getResources().getBoolean(
-                Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mUseFixedVolume = mAudioManager.isVolumeFixed();
+        mHdmiControlManager = mContext.getSystemService(HdmiControlManager.class);
+        if(mHdmiControlManager != null) {
+            mHdmiEnableStatus = mHdmiControlManager.getHdmiCecEnabled();
+            mHdmiControlManager.setHdmiCecEnabled(HdmiControlManager.HDMI_CEC_CONTROL_DISABLED);
+        }
 
         mStreamVolumeMap.clear();
         for (Integer stream : ALL_VOLUME_STREAMS) {
@@ -133,6 +144,9 @@
             mSession.release();
             mSession = null;
         }
+        if (mHdmiControlManager != null) {
+            mHdmiControlManager.setHdmiCecEnabled(mHdmiEnableStatus);
+        }
 
         try {
             mActivityRule.finishActivity();
@@ -157,6 +171,7 @@
     @Test
     public void testVolumeKey_whileSessionAlive() throws Exception {
         if (mUseFixedVolume) {
+            Log.i(TAG, "testVolumeKey_whileSessionAlive skipped due to full volume device");
             return;
         }
 
@@ -185,6 +200,7 @@
     @Test
     public void testVolumeKey_afterSessionReleased() throws Exception {
         if (mUseFixedVolume) {
+            Log.i(TAG, "testVolumeKey_afterSessionReleased skipped due to full volume device");
             return;
         }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 20d0f50..762ed74 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -153,6 +153,37 @@
         return ds;
     }
 
+    public void testExceptionWhileClosingMediaDataSource() throws IOException {
+        MediaDataSource backingMediaDataSource =
+                TestMediaDataSource.fromAssetFd(
+                        getAssetFileDescriptorFor("audio_with_metadata.mp3"));
+        MediaDataSource mediaDataSource = new MediaDataSource() {
+            @Override
+            public int readAt(long position, byte[] buffer, int offset, int size)
+                    throws IOException {
+                return backingMediaDataSource.readAt(position, buffer, offset, size);
+            }
+
+            @Override
+            public long getSize() throws IOException {
+                return backingMediaDataSource.getSize();
+            }
+
+            @Override
+            public void close() throws IOException {
+                backingMediaDataSource.close();
+                throw new IOException();
+            }
+        };
+        mRetriever.setDataSource(mediaDataSource);
+        try {
+            mRetriever.release();
+            fail("Expected IOException not thrown.");
+        } catch (IOException e) {
+            // Expected.
+        }
+    }
+
     public void testAudioMetadata() {
         setDataSourceCallback("audio_with_metadata.mp3");
 
@@ -387,13 +418,6 @@
     }
 
     public void testID3v240ExtHeader() {
-        if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
-            // The fix for b/154357105 was released in mainline release 30.09.007.01
-            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
-            if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
-                return;
-            }
-        }
         setDataSourceFd("sinesweepid3v24ext.mp3");
         assertEquals("Mime type was other than expected",
                 "audio/mpeg",
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
index 5508dfa..61c2ddb 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionManagerTest.java
@@ -38,6 +38,7 @@
 import android.provider.Settings;
 import android.test.InstrumentationTestCase;
 import android.test.UiThreadTest;
+import android.text.TextUtils;
 import android.view.KeyEvent;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
@@ -579,6 +580,7 @@
         }
     }
 
+    @NonMediaMainlineTest
     public void testIsTrustedForMediaControl_withInvalidUid() throws Exception {
         List<String> packageNames = getEnabledNotificationListenerPackages();
         for (String packageName : packageNames) {
@@ -627,7 +629,7 @@
                 Settings.Secure.getString(
                         mContext.getContentResolver(),
                         ENABLED_NOTIFICATION_LISTENERS);
-        if (enabledNotificationListeners != null) {
+        if (!TextUtils.isEmpty(enabledNotificationListeners)) {
             String[] components = enabledNotificationListeners.split(":");
             for (String componentString : components) {
                 ComponentName component = ComponentName.unflattenFromString(componentString);
diff --git a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
index 3623f15..84c4684 100644
--- a/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/SystemMediaRouter2Test.java
@@ -113,7 +113,8 @@
         mContext = InstrumentationRegistry.getTargetContext();
         mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mUiAutomation.adoptShellPermissionIdentity(Manifest.permission.MEDIA_CONTENT_CONTROL,
-                                                   Manifest.permission.QUERY_AUDIO_STATE);
+                Manifest.permission.MODIFY_AUDIO_ROUTING,
+                Manifest.permission.QUERY_AUDIO_STATE);
 
         mExecutor = Executors.newSingleThreadExecutor();
         mAudioManager = (AudioManager) mContext.getSystemService(AUDIO_SERVICE);
@@ -1023,7 +1024,7 @@
 
     private void releaseAllSessions() {
         MediaRouter2Manager manager = MediaRouter2Manager.getInstance(mContext);
-        for (RoutingSessionInfo session : manager.getActiveSessions()) {
+        for (RoutingSessionInfo session : manager.getRemoteSessions()) {
             manager.releaseSession(session);
         }
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d5d01c8..0df3b1b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -162,6 +162,8 @@
             }
         }
 
+        Preconditions.assertTestFileExists(mediaName);
+
         File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt");
         Writer output = new BufferedWriter(new FileWriter(playbackOutput, true));
 
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
new file mode 100644
index 0000000..6fc2b8a
--- /dev/null
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
@@ -0,0 +1,37 @@
+/*
+ * 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.mediastress.cts;
+
+import java.io.File;
+
+import junit.framework.Assert;
+
+/**
+ * Static methods used to validate preconditions in the media CTS suite
+ * to simplify failure diagnosis.
+ */
+
+public final class Preconditions {
+    private static final String TAG = "Preconditions";
+
+    public static void assertTestFileExists(String pathName) {
+        File testFile = new File(pathName);
+        Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists());
+    }
+
+    private Preconditions() {}
+}
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 0b889ae..6a220a3 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -19,10 +19,12 @@
 android_test {
     name: "CtsMediaTranscodingTestCases",
     defaults: ["CtsMediaTranscodingTestCasesDefaults", "cts_defaults"],
-    min_sdk_version: "31",
+    // part of MTS, so we need compatibility back to Q/29
+    min_sdk_version: "29",
     test_suites: [
         "cts",
         "general-tests",
+        "mts-media",
         "mts",
     ],
     static_libs: [
@@ -43,5 +45,4 @@
         "android.test.base",
         "android.test.runner",
     ],
-    sdk_version: "test_current",
 }
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1618b00..c69754a 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -25,6 +25,12 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
+    <!-- included in mainline testing, so must run back on 29.
+         Transcoding was introduced in 31 -->
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="android.media.mediatranscoding.cts"
             android:label="Tests for MediaTranscoding.">
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index ba40ca5..6e63624 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -27,6 +27,8 @@
 import android.test.AndroidTestCase;
 import android.util.Xml;
 
+import androidx.test.filters.SdkSuppress;
+
 import org.junit.Test;
 import org.xmlpull.v1.XmlPullParser;
 
@@ -36,6 +38,7 @@
 
 @Presubmit
 @AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
 public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
     private static final String TAG = "ApplicationMediaCapabilitiesTest";
 
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index 78e7b33..fd99ed0 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -33,6 +33,7 @@
 import android.media.MediaTranscodingManager.TranscodingSession;
 import android.media.MediaTranscodingManager.VideoTranscodingRequest;
 import android.net.Uri;
+// import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -44,6 +45,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.MediaUtils;
@@ -70,6 +72,7 @@
 @Presubmit
 @RequiresDevice
 @AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
 public class MediaTranscodingManagerTest extends AndroidTestCase {
     private static final String TAG = "MediaTranscodingManagerTest";
     /** The time to wait for the transcode operation to complete before failing the test. */
@@ -195,6 +198,7 @@
 
     // Skip the test for TV, Car and Watch devices.
     private boolean shouldSkip() {
+
         PackageManager pm =
                 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
         return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
diff --git a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
index 4ed2853..8d359bd 100644
--- a/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
+++ b/tests/tests/midi/src/android/midi/cts/MidiEchoTest.java
@@ -18,25 +18,24 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.media.midi.MidiManager;
-import android.media.midi.MidiOutputPort;
 import android.media.midi.MidiDevice;
-import android.media.midi.MidiDevice.MidiConnection;
 import android.media.midi.MidiDeviceInfo;
 import android.media.midi.MidiDeviceInfo.PortInfo;
 import android.media.midi.MidiDeviceStatus;
 import android.media.midi.MidiInputPort;
+import android.media.midi.MidiManager;
+import android.media.midi.MidiOutputPort;
 import android.media.midi.MidiReceiver;
-import android.media.midi.MidiSender;
 import android.os.Bundle;
-import android.util.Log;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 import com.android.midi.CTSMidiEchoTestService;
 import com.android.midi.MidiEchoTestService;
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Random;
 
 /**
@@ -307,6 +306,8 @@
 
         assertEquals("MIDI device type", MidiDeviceInfo.TYPE_VIRTUAL,
                 echoInfo.getType());
+        assertEquals("MIDI default protocol", MidiDeviceInfo.PROTOCOL_UNKNOWN,
+                echoInfo.getDefaultProtocol());
     }
 
     // Is the MidiManager supported?
@@ -324,6 +325,14 @@
         MidiDeviceInfo[] infos = midiManager.getDevices();
         assertTrue("device list was null", infos != null);
         assertTrue("device list was empty", infos.length >= 1);
+
+        Collection<MidiDeviceInfo> legacyDeviceInfos = midiManager.getDevicesForTransport(
+                MidiManager.TRANSPORT_MIDI_BYTE_STREAM);
+        assertTrue("Legacy Device list was null.", legacyDeviceInfos != null);
+        assertTrue("Legacy Device list was empty", legacyDeviceInfos.size() >= 1);
+        Collection<MidiDeviceInfo> universalDeviceInfos = midiManager.getDevicesForTransport(
+                MidiManager.TRANSPORT_UNIVERSAL_MIDI_PACKETS);
+        assertTrue("Universal Device list was null.", universalDeviceInfos != null);
     }
 
     public void testDeviceInfo() throws Exception {
diff --git a/tests/tests/multiuser/Android.bp b/tests/tests/multiuser/Android.bp
index c930a43..2ffc0f2 100644
--- a/tests/tests/multiuser/Android.bp
+++ b/tests/tests/multiuser/Android.bp
@@ -28,6 +28,9 @@
     static_libs: [
         "ctstestrunner-axt",
         "compatibility-device-util-axt",
+        "Nene",
+        "Harrier",
+        "TestApp",
     ],
     libs: ["android.test.base"],
     sdk_version: "test_current",
diff --git a/tests/tests/multiuser/AndroidManifest.xml b/tests/tests/multiuser/AndroidManifest.xml
index 17a71d7..b660662 100644
--- a/tests/tests/multiuser/AndroidManifest.xml
+++ b/tests/tests/multiuser/AndroidManifest.xml
@@ -19,8 +19,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.multiuser.cts"
           android:targetSandboxVersion="2">
-    <!-- Must be at least 30, otherwise some tests could be invalid. -->
-    <uses-sdk android:targetSdkVersion="30"/>
+    <!-- Must be at least 33, otherwise some tests could be invalid. -->
+    <uses-sdk android:targetSdkVersion="33"/>
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/multiuser/AndroidTest.xml b/tests/tests/multiuser/AndroidTest.xml
index df4a765..5286139 100644
--- a/tests/tests/multiuser/AndroidTest.xml
+++ b/tests/tests/multiuser/AndroidTest.xml
@@ -20,6 +20,7 @@
     <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" />
+    <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsMultiUserTestCases.apk" />
@@ -27,5 +28,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.multiuser.cts" />
         <option name="runtime-hint" value="8m" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
     </test>
 </configuration>
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java b/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java
new file mode 100644
index 0000000..ccabd9d
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/NewUserRequestTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.multiuser.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.os.NewUserRequest;
+import org.junit.Test;
+
+public class NewUserRequestTest {
+
+    @Test
+    public void testSetName() {
+        String name = "testUser";
+        NewUserRequest request = new NewUserRequest.Builder().setName(name).build();
+
+        assertThat(request.getName()).isEqualTo(name);
+    }
+
+    @Test
+    public void testSetNameNull() {
+        NewUserRequest request = new NewUserRequest.Builder().setName(null).build();
+
+        assertThat(request.getName()).isNull();
+    }
+
+    @Test
+    public void testSetAdmin() {
+        NewUserRequest request = new NewUserRequest.Builder().setAdmin().build();
+
+        assertThat(request.isAdmin()).isTrue();
+    }
+
+    @Test
+    public void testBuildThrowsOnNullUserType() {
+        assertThrows(IllegalStateException.class,
+                () -> new NewUserRequest.Builder().setUserType(null).setAdmin().build());
+    }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java b/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java
new file mode 100644
index 0000000..65447d3
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/NewUserResponseTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.multiuser.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.NewUserResponse;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Test;
+
+public final class NewUserResponseTest {
+
+    @Test
+    public void testNewUserResponseSuccessful() {
+        UserHandle userHandle = UserHandle.of(100);
+
+        NewUserResponse response =
+                new NewUserResponse(userHandle, UserManager.USER_OPERATION_SUCCESS);
+
+        assertThat(response.isSuccessful()).isTrue();
+        assertThat(response.getUser()).isEqualTo(userHandle);
+        assertThat(response.getOperationResult()).isEqualTo(UserManager.USER_OPERATION_SUCCESS);
+    }
+
+    @Test
+    public void testNewUserResponseUnsuccessful() {
+        NewUserResponse response = new NewUserResponse(/* user= */ null,
+                UserManager.USER_OPERATION_ERROR_UNKNOWN);
+
+        assertThat(response.isSuccessful()).isFalse();
+        assertThat(response.getUser()).isNull();
+        assertThat(response.getOperationResult())
+                .isEqualTo(UserManager.USER_OPERATION_ERROR_UNKNOWN);
+    }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.java b/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.java
new file mode 100644
index 0000000..5211498
--- /dev/null
+++ b/tests/tests/multiuser/src/android/multiuser/cts/PermissionHelper.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 android.multiuser.cts;
+
+import android.app.Instrumentation;
+
+public class PermissionHelper implements AutoCloseable {
+    private final Instrumentation mInstrumentation;
+
+    private PermissionHelper(Instrumentation instrumentation, String... permissions) {
+        if (instrumentation == null) {
+            throw new IllegalArgumentException("instrumentation must not be null");
+        }
+        mInstrumentation = instrumentation;
+        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(permissions);
+    }
+
+    @Override
+    public void close() {
+        mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+    }
+
+    public static PermissionHelper adoptShellPermissionIdentity(
+            Instrumentation instrumentation, String... permissions) {
+        return new PermissionHelper(instrumentation, permissions);
+    }
+}
diff --git a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
index 78e0619..ff01b59 100644
--- a/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
+++ b/tests/tests/multiuser/src/android/multiuser/cts/UserManagerTest.java
@@ -16,46 +16,85 @@
 
 package android.multiuser.cts;
 
+import static android.Manifest.permission.CREATE_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.content.pm.PackageManager.FEATURE_MANAGED_USERS;
+import static android.multiuser.cts.PermissionHelper.adoptShellPermissionIdentity;
 import static android.multiuser.cts.TestingUtils.getBooleanProperty;
+import static android.os.UserManager.USER_OPERATION_SUCCESS;
+import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import android.Manifest;
+import static org.junit.Assume.assumeTrue;
+
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.pm.UserInfo;
+import android.graphics.Bitmap;
+import android.os.Build;
+import android.os.NewUserRequest;
+import android.os.NewUserResponse;
+import android.os.PersistableBundle;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SystemUserOnly;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.RequireFeature;
+import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.users.UserReference;
+import com.android.bedstead.nene.users.UserType;
+
 import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
 
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-@RunWith(JUnit4.class)
+@RunWith(BedsteadJUnit4.class)
 public final class UserManagerTest {
 
-    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
-    private final Context mContext = mInstrumentation.getContext();
+    @ClassRule
+    @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
 
+    private static final Context sContext = TestApis.context().instrumentedContext();
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
     private UserManager mUserManager;
 
-    @Before
-    public void setTestFixtures() {
-        mUserManager = mContext.getSystemService(UserManager.class);
+    private final String mAccountName = "test_account_name";
+    private final String mAccountType = "test_account_type";
 
+    @Before
+    public void setUp() {
+        mUserManager = sContext.getSystemService(UserManager.class);
         assertWithMessage("UserManager service").that(mUserManager).isNotNull();
     }
 
+    private void removeUser(UserHandle userHandle) {
+        if (userHandle == null) {
+            return;
+        }
+
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            assertThat(mUserManager.removeUser(userHandle)).isTrue();
+        }
+    }
+
     /**
      * Verify that the isUserAGoat() method always returns false for API level 30. This is
      * because apps targeting R no longer have access to package queries by default.
@@ -84,30 +123,174 @@
     @Test
     @SystemUserOnly(reason = "Profiles are only supported on system user.")
     public void testCloneUser() throws Exception {
-        // Need CREATE_USERS permission to create user in test
-        mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
-                Manifest.permission.CREATE_USERS, Manifest.permission.INTERACT_ACROSS_USERS);
-        Set<String> disallowedPackages = new HashSet<String>();
-        UserHandle userHandle = mUserManager.createProfile(
-                "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
-        assertThat(userHandle).isNotNull();
+        UserHandle userHandle = null;
 
-        try {
-            final Context userContext = mContext.createPackageContextAsUser("system", 0,
+        // Need CREATE_USERS permission to create user in test
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            Set<String> disallowedPackages = new HashSet<String>();
+            userHandle = mUserManager.createProfile(
+                    "Clone user", UserManager.USER_TYPE_PROFILE_CLONE, disallowedPackages);
+            assertThat(userHandle).isNotNull();
+
+            final Context userContext = sContext.createPackageContextAsUser("system", 0,
                     userHandle);
             final UserManager cloneUserManager = userContext.getSystemService(UserManager.class);
             assertThat(cloneUserManager.isMediaSharedWithParent()).isTrue();
             assertThat(cloneUserManager.isCloneProfile()).isTrue();
 
             List<UserInfo> list = mUserManager.getUsers(true, true, true);
+            final UserHandle finalUserHandle = userHandle;
             List<UserInfo> cloneUsers = list.stream().filter(
-                    user -> (user.id == userHandle.getIdentifier()
+                    user -> (user.id == finalUserHandle.getIdentifier()
                             && user.isCloneProfile()))
                     .collect(Collectors.toList());
             assertThat(cloneUsers.size()).isEqualTo(1);
         } finally {
-            assertThat(mUserManager.removeUser(userHandle)).isTrue();
-            mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+            removeUser(userHandle);
         }
     }
+
+    @Test
+    @SystemUserOnly(reason = "Restricted users are only supported on system user.")
+    public void testRestrictedUser() throws Exception {
+        UserHandle user = null;
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            // Check that the SYSTEM user is not restricted.
+            assertThat(mUserManager.isRestrictedProfile()).isFalse();
+            assertThat(mUserManager.isRestrictedProfile(UserHandle.SYSTEM)).isFalse();
+            assertThat(mUserManager.getRestrictedProfileParent()).isNull();
+
+            final UserInfo info = mUserManager.createRestrictedProfile("Restricted user");
+
+            // If the device supports Restricted users, it must report it correctly.
+            assumeTrue("Couldn't create a restricted profile", info != null);
+
+            user = UserHandle.of(info.id);
+            assertThat(mUserManager.isRestrictedProfile(user)).isTrue();
+
+            final Context userContext = sContext.createPackageContextAsUser("system", 0, user);
+            final UserManager userUm = userContext.getSystemService(UserManager.class);
+            // TODO(183239043): Remove the if{} clause after v33 Sdk bump.
+            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S_V2) {
+                assertThat(userUm.isRestrictedProfile()).isTrue();
+            }
+            assertThat(userUm.getRestrictedProfileParent().isSystem()).isTrue();
+        } finally {
+            removeUser(user);
+        }
+    }
+
+    private NewUserRequest newUserRequest() {
+        final PersistableBundle accountOptions = new PersistableBundle();
+        accountOptions.putString("test_account_option_key", "test_account_option_value");
+
+        return new NewUserRequest.Builder()
+                .setName("test_user")
+                .setUserType(USER_TYPE_FULL_SECONDARY)
+                .setUserIcon(Bitmap.createBitmap(32, 32, Bitmap.Config.RGB_565))
+                .setAccountName(mAccountName)
+                .setAccountType(mAccountType)
+                .setAccountOptions(accountOptions)
+                .build();
+    }
+
+    @Test
+    public void testSomeUserHasAccount() {
+        UserHandle user = null;
+
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isFalse();
+            user = mUserManager.createUser(newUserRequest()).getUser();
+            assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isTrue();
+        } finally {
+            removeUser(user);
+        }
+    }
+
+    @Test
+    public void testSomeUserHasAccount_shouldIgnoreToBeRemovedUsers() {
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            final NewUserResponse response = mUserManager.createUser(newUserRequest());
+            assertThat(response.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+            mUserManager.removeUser(response.getUser());
+            assertThat(mUserManager.someUserHasAccount(mAccountName, mAccountType)).isFalse();
+        }
+    }
+
+    @Test
+    public void testCreateUser_withNewUserRequest_shouldCreateUserWithCorrectProperties()
+            throws PackageManager.NameNotFoundException {
+        UserHandle user = null;
+
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            final NewUserRequest request = newUserRequest();
+            final NewUserResponse response = mUserManager.createUser(request);
+            user = response.getUser();
+
+            assertThat(response.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+            assertThat(response.isSuccessful()).isTrue();
+            assertThat(user).isNotNull();
+
+            UserManager userManagerOfNewUser = sContext
+                    .createPackageContextAsUser("android", 0, user)
+                    .getSystemService(UserManager.class);
+
+            assertThat(userManagerOfNewUser.getUserName()).isEqualTo(request.getName());
+            assertThat(userManagerOfNewUser.getUserType()).isEqualTo(request.getUserType());
+            // We can not test userIcon and accountOptions,
+            // because getters require MANAGE_USERS permission.
+            // And we are already testing accountName and accountType
+            // are set correctly in testSomeUserHasAccount method.
+        } finally {
+            removeUser(user);
+        }
+    }
+
+    @Test
+    public void testCreateUser_withNewUserRequest_shouldNotAllowDuplicateUserAccounts() {
+        UserHandle user1 = null;
+        UserHandle user2 = null;
+
+        try (PermissionHelper ph = adoptShellPermissionIdentity(mInstrumentation, CREATE_USERS)) {
+            final NewUserResponse response1 = mUserManager.createUser(newUserRequest());
+            user1 = response1.getUser();
+
+            assertThat(response1.getOperationResult()).isEqualTo(USER_OPERATION_SUCCESS);
+            assertThat(response1.isSuccessful()).isTrue();
+            assertThat(user1).isNotNull();
+
+            final NewUserResponse response2 = mUserManager.createUser(newUserRequest());
+            user2 = response2.getUser();
+
+            assertThat(response2.getOperationResult()).isEqualTo(
+                    UserManager.USER_OPERATION_ERROR_USER_ACCOUNT_ALREADY_EXISTS);
+            assertThat(response2.isSuccessful()).isFalse();
+            assertThat(user2).isNull();
+        } finally {
+            removeUser(user1);
+            removeUser(user2);
+        }
+    }
+
+    @Test
+    @AppModeFull
+    @RequireFeature(FEATURE_MANAGED_USERS)
+    @EnsureHasPermission(INTERACT_ACROSS_USERS)
+    public void getProfileParent_withNewlyCreatedProfile() {
+        final UserReference parent = TestApis.users().instrumented();
+        try (UserReference profile = TestApis.users().createUser()
+                .parent(parent)
+                .type(TestApis.users().supportedType(UserType.MANAGED_PROFILE_TYPE_NAME))
+                .createAndStart()) {
+            assertThat(mUserManager.getProfileParent(profile.userHandle()))
+                    .isEqualTo(parent.userHandle());
+        }
+    }
+
+    @Test
+    @AppModeFull
+    @EnsureHasPermission(INTERACT_ACROSS_USERS)
+    public void getProfileParent_returnsNullForNonProfile() {
+        assertThat(mUserManager.getProfileParent(TestApis.users().system().userHandle())).isNull();
+    }
 }
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
index 1b8cf44..78e009b 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
@@ -142,6 +142,27 @@
         }
     };
 
+    void createAndVerifyHonoringMMap() {
+        aaudio_policy_t originalPolicy = AAUDIO_POLICY_AUTO;
+
+        // Turn off MMap if requested.
+        bool allowMMap = std::get<PARAM_ALLOW_MMAP>(GetParam()) == MMAP_ALLOWED;
+        if (AAudioExtensions::getInstance().isMMapSupported()) {
+            originalPolicy = AAudioExtensions::getInstance().getMMapPolicy();
+            AAudioExtensions::getInstance().setMMapEnabled(allowMMap);
+        }
+
+        mHelper->createAndVerifyStream(&mSetupSuccessful);
+
+        // Restore policy for next test.
+        if (AAudioExtensions::getInstance().isMMapSupported()) {
+            AAudioExtensions::getInstance().setMMapPolicy(originalPolicy);
+        }
+        if (!allowMMap) {
+            ASSERT_FALSE(AAudioExtensions::getInstance().isMMapUsed(mHelper->stream()));
+        }
+    }
+
     static void MyErrorCallbackProc(AAudioStream *stream, void *userData, aaudio_result_t error);
 
     AAudioStreamBuilder* builder() const { return mHelper->builder(); }
@@ -149,7 +170,7 @@
     const StreamBuilderHelper::Parameters& actual() const { return mHelper->actual(); }
 
     std::unique_ptr<T> mHelper;
-    bool mSetupSuccesful = false;
+    bool mSetupSuccessful = false;
     std::unique_ptr<AAudioCallbackTestData> mCbData;
 };
 
@@ -179,9 +200,8 @@
 }
 
 void AAudioInputStreamCallbackTest::SetUp() {
-    aaudio_policy_t originalPolicy = AAUDIO_POLICY_AUTO;
 
-    mSetupSuccesful = false;
+    mSetupSuccessful = false;
     if (!deviceSupportsFeature(FEATURE_RECORDING)) return;
     mHelper.reset(new InputStreamBuilderHelper(
                     std::get<PARAM_SHARING_MODE>(GetParam()),
@@ -198,28 +218,12 @@
         AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
     }
 
-    // Turn off MMap if requested.
-    int allowMMap = std::get<PARAM_ALLOW_MMAP>(GetParam()) == MMAP_ALLOWED;
-    if (AAudioExtensions::getInstance().isMMapSupported()) {
-        originalPolicy = AAudioExtensions::getInstance().getMMapPolicy();
-        AAudioExtensions::getInstance().setMMapEnabled(allowMMap);
-    }
-
-    mHelper->createAndVerifyStream(&mSetupSuccesful);
-
-    // Restore policy for next test.
-    if (AAudioExtensions::getInstance().isMMapSupported()) {
-        AAudioExtensions::getInstance().setMMapPolicy(originalPolicy);
-    }
-    if (!allowMMap) {
-        ASSERT_FALSE(AAudioExtensions::getInstance().isMMapUsed(mHelper->stream()));
-    }
-
+    createAndVerifyHonoringMMap();
 }
 
 // Test starting and stopping an INPUT AAudioStream that uses a Callback
 TEST_P(AAudioInputStreamCallbackTest, testRecording) {
-    if (!mSetupSuccesful) return;
+    if (!mSetupSuccessful) return;
 
     const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
     const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
@@ -346,7 +350,7 @@
 }
 
 void AAudioOutputStreamCallbackTest::SetUp() {
-    mSetupSuccesful = false;
+    mSetupSuccessful = false;
     if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
     mHelper.reset(new OutputStreamBuilderHelper(
                     std::get<PARAM_SHARING_MODE>(GetParam()),
@@ -363,13 +367,13 @@
         AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
     }
 
-    mHelper->createAndVerifyStream(&mSetupSuccesful);
+    createAndVerifyHonoringMMap();
 
 }
 
 // Test starting and stopping an OUTPUT AAudioStream that uses a Callback
 TEST_P(AAudioOutputStreamCallbackTest, testPlayback) {
-    if (!mSetupSuccesful) return;
+    if (!mSetupSuccessful) return;
 
     const int32_t framesPerDataCallback = std::get<PARAM_FRAMES_PER_CB>(GetParam());
     const int32_t streamFramesPerDataCallback = AAudioStream_getFramesPerDataCallback(stream());
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
index cc811cd..047419b 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
@@ -82,6 +82,7 @@
 static void try_opening_audio_stream(AAudioStreamBuilder *aaudioBuilder, Expect expect) {
     // Create an AAudioStream using the Builder.
     AAudioStream *aaudioStream = nullptr;
+    int64_t beforeTimeNanos = getNanoseconds();
     aaudio_result_t result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
     if (expect == Expect::FAIL) {
         ASSERT_NE(AAUDIO_OK, result);
@@ -94,10 +95,19 @@
                 || ((result == AAUDIO_OK) && (aaudioStream != nullptr)));
     }
 
+    // The stream should be open within one second.
+    static const int64_t kNanosPerSecond = 1e9;
+    ASSERT_LT(getNanoseconds() - beforeTimeNanos, kNanosPerSecond)
+            << "It took more than one second to open stream";
+
     // Cleanup
     ASSERT_EQ(AAUDIO_OK, AAudioStreamBuilder_delete(aaudioBuilder));
     if (aaudioStream != nullptr) {
+        beforeTimeNanos = getNanoseconds();
         ASSERT_EQ(AAUDIO_OK, AAudioStream_close(aaudioStream));
+        // The stream should be closed within one second.
+        ASSERT_LT(getNanoseconds() - beforeTimeNanos, kNanosPerSecond)
+                << "It took more than one second to close stream";
     }
 }
 
@@ -624,3 +634,67 @@
                 std::make_pair(AAUDIO_DIRECTION_INPUT, AAUDIO_CHANNEL_5POINT1),
                 std::make_pair(AAUDIO_DIRECTION_INPUT, AAUDIO_UNSPECIFIED)),
         &AAudioStreamBuilderChannelMaskAndCountTest::getTestName);
+
+using CommonCombinationTestParams = std::tuple<aaudio_direction_t,
+                                               aaudio_sharing_mode_t,
+                                               aaudio_performance_mode_t,
+                                               int32_t /*sample rate*/,
+                                               aaudio_format_t,
+                                               aaudio_channel_mask_t>;
+enum {
+    PARAM_DIRECTION = 0,
+    PARAM_SHARING_MODE,
+    PARAM_PERFORMANCE_MODE,
+    PARAM_SAMPLE_RATE,
+    PARAM_FORMAT,
+    PARAM_CHANNEL_MASK
+};
+class AAudioStreamBuilderCommonCombinationTest :
+        public ::testing::TestWithParam<CommonCombinationTestParams> {
+  public:
+    static std::string getTestName(
+            const ::testing::TestParamInfo<CommonCombinationTestParams>& info) {
+        std::stringstream ss;
+        ss << (std::get<PARAM_DIRECTION>(info.param) == AAUDIO_DIRECTION_INPUT ? "INPUT_"
+                                                                               : "OUTPUT_")
+           << sharingModeToString(std::get<PARAM_SHARING_MODE>(info.param)) << "_"
+           << performanceModeToString(std::get<PARAM_PERFORMANCE_MODE>(info.param)) << "_"
+           << "sampleRate_" << std::get<PARAM_SAMPLE_RATE>(info.param) << "_"
+           << "format_0x" << std::hex << std::get<PARAM_FORMAT>(info.param) << "_"
+           << "channelMask_0x" << std::get<PARAM_CHANNEL_MASK>(info.param) << "";
+        return ss.str();
+    }
+};
+
+TEST_P(AAudioStreamBuilderCommonCombinationTest, openStream) {
+    if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
+    AAudioStreamBuilder *aaudioBuilder = nullptr;
+    create_stream_builder(&aaudioBuilder);
+    const auto param = GetParam();
+    AAudioStreamBuilder_setDirection(aaudioBuilder, std::get<PARAM_DIRECTION>(param));
+    AAudioStreamBuilder_setSharingMode(aaudioBuilder, std::get<PARAM_SHARING_MODE>(param));
+    AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, std::get<PARAM_PERFORMANCE_MODE>(param));
+    AAudioStreamBuilder_setSampleRate(aaudioBuilder, std::get<PARAM_SAMPLE_RATE>(param));
+    AAudioStreamBuilder_setFormat(aaudioBuilder, std::get<PARAM_FORMAT>(param));
+    AAudioStreamBuilder_setChannelMask(aaudioBuilder, std::get<PARAM_CHANNEL_MASK>(param));
+    // All the test parameters all reasonable values with different combination. In that case,
+    // it is expected that the opening will be successful.
+    try_opening_audio_stream(aaudioBuilder, Expect::SUCCEED);
+}
+
+INSTANTIATE_TEST_CASE_P(CommonComb, AAudioStreamBuilderCommonCombinationTest,
+        ::testing::Combine(
+                ::testing::Values(AAUDIO_DIRECTION_OUTPUT, AAUDIO_DIRECTION_INPUT),
+                ::testing::Values(AAUDIO_SHARING_MODE_SHARED, AAUDIO_SHARING_MODE_EXCLUSIVE),
+                ::testing::Values(
+                        AAUDIO_PERFORMANCE_MODE_NONE,
+                        AAUDIO_PERFORMANCE_MODE_POWER_SAVING,
+                        AAUDIO_PERFORMANCE_MODE_LOW_LATENCY),
+                ::testing::Values(// Sample rate
+                        AAUDIO_UNSPECIFIED, 8000, 16000, 44100, 48000, 96000, 192000),
+                ::testing::Values(
+                        AAUDIO_UNSPECIFIED,
+                        AAUDIO_FORMAT_PCM_I16,
+                        AAUDIO_FORMAT_PCM_FLOAT),
+                ::testing::Values(AAUDIO_CHANNEL_MONO, AAUDIO_CHANNEL_STEREO)),
+        &AAudioStreamBuilderCommonCombinationTest::getTestName);
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.cpp b/tests/tests/nativemedia/aaudio/jni/utils.cpp
index a9ed984..ebe2f56 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/utils.cpp
@@ -214,13 +214,6 @@
     AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, kBufferCapacityFrames);
 }
 
-void OutputStreamBuilderHelper::createAndVerifyStream(bool *success) {
-    StreamBuilderHelper::createAndVerifyStream(success);
-    if (*success) {
-        ASSERT_GE(AAudioStream_getBufferCapacityInFrames(mStream), kBufferCapacityFrames);
-    }
-}
-
 AAudioExtensions::AAudioExtensions()
     : mMMapSupported(isPolicyEnabled(getMMapPolicyProperty()))
     , mMMapExclusiveSupported(isPolicyEnabled(getIntegerProperty(
@@ -267,4 +260,5 @@
     }
     mFunctionsLoaded = true;
     return mFunctionsLoaded;
-}
\ No newline at end of file
+}
+
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.h b/tests/tests/nativemedia/aaudio/jni/utils.h
index 8294eca..5674496 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.h
+++ b/tests/tests/nativemedia/aaudio/jni/utils.h
@@ -115,7 +115,6 @@
             aaudio_performance_mode_t requestedPerfMode,
             aaudio_format_t requestedFormat = AAUDIO_FORMAT_PCM_I16);
     void initBuilder();
-    void createAndVerifyStream(bool *success);
 
   private:
     const int32_t kBufferCapacityFrames = 2000;
diff --git a/tests/tests/nativemedia/resourceobserver/OWNERS b/tests/tests/nativemedia/resourceobserver/OWNERS
index 5679184..167c365 100644
--- a/tests/tests/nativemedia/resourceobserver/OWNERS
+++ b/tests/tests/nativemedia/resourceobserver/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 1344
-chz@google.com
 wonsik@google.com
 lajos@google.com
diff --git a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
index 960149b..d27bac5 100644
--- a/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
+++ b/tests/tests/nativemidi/java/android/nativemidi/cts/NativeMidiEchoTest.java
@@ -21,7 +21,6 @@
 import android.media.midi.MidiDevice;
 import android.media.midi.MidiDeviceInfo;
 import android.media.midi.MidiManager;
-import android.os.Bundle;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
@@ -474,6 +473,20 @@
         Assert.assertEquals("failed pure native latency test.", 0, result);
     }
 
+    /**
+     * Checks that getDefaultProtocol returns a valid value.
+     */
+    @Test
+    public void test_J_GetDefaultProtocol() throws Exception {
+        if (!hasMidiSupport()) {
+            return;
+        }
+
+        int defaultProtocol = getDefaultProtocol(mTestContext);
+        Assert.assertEquals("default protocol incorrect.",
+                MidiDeviceInfo.PROTOCOL_UNKNOWN, defaultProtocol);
+    }
+
     // Native Routines
     public static native void initN();
 
@@ -507,4 +520,7 @@
     // Pure Native Checks
     public native int matchNativeMessages(long ctx);
     public native int checkNativeLatency(long ctx, long maxLatencyNanos);
+
+    // AMidiDevice getters
+    public native int getDefaultProtocol(long ctx);
 }
diff --git a/tests/tests/nativemidi/jni/native-lib.cpp b/tests/tests/nativemidi/jni/native-lib.cpp
index 5a2f0ca..00cfb08 100644
--- a/tests/tests/nativemidi/jni/native-lib.cpp
+++ b/tests/tests/nativemidi/jni/native-lib.cpp
@@ -544,4 +544,13 @@
     return ((TestContext*)ctx)->checkInOutLatency(maxLatencyNanos);
 }
 
+JNIEXPORT jint JNICALL Java_android_nativemidi_cts_NativeMidiEchoTest_getDefaultProtocol(
+        JNIEnv*, jobject, jlong ctx) {
+
+    TestContext* context = (TestContext*)ctx;
+
+    return AMidiDevice_getDefaultProtocol(context->nativeDevice);
+}
+
+
 } // extern "C"
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
index affcee1..5a0d2ad 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy20/Android.bp
@@ -25,6 +25,8 @@
         "ctstestrunner-axt",
         "androidx.test.rules",
         "junit",
+        "permission-test-util-lib",
+        "compatibility-device-util-axt",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
index 8d2931f..e5564e3 100644
--- a/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy20/src/android/app/notification/legacy20/cts/LegacyNotificationManager20Test.java
@@ -16,6 +16,10 @@
 
 package android.app.notification.legacy20.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+
 import static junit.framework.Assert.fail;
 
 import static org.junit.Assert.assertNotNull;
@@ -33,6 +37,9 @@
 import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.provider.Telephony.Threads;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
@@ -40,6 +47,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import junit.framework.Assert;
 
 import org.junit.After;
@@ -68,6 +77,7 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
         mNotificationManager = (NotificationManager) mContext.getSystemService(
@@ -79,6 +89,15 @@
 
     @After
     public void tearDown() throws Exception {
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
         Thread.sleep(500); // wait for listener to disconnect
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
index 144ceac..b6033e1 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy27/Android.bp
@@ -25,6 +25,8 @@
         "ctstestrunner-axt",
         "androidx.test.rules",
         "junit",
+        "permission-test-util-lib",
+        "compatibility-device-util-axt",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
index 642dc72..4fb4917 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/LegacyNotificationManagerTest.java
@@ -16,6 +16,7 @@
 
 package android.app.notification.legacy.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCREEN_INTENT;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
 import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
@@ -28,17 +29,16 @@
 
 import static org.junit.Assert.assertTrue;
 
-import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.app.Notification;
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.UiAutomation;
-import android.content.pm.PackageManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.provider.Telephony.Threads;
@@ -75,6 +75,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(POST_NOTIFICATIONS);
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
         toggleListenerAccess(SecondaryNotificationListener.getId(),
@@ -87,6 +89,8 @@
 
     @After
     public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
         toggleListenerAccess(SecondaryNotificationListener.getId(),
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
index 6a52f46..794ecf4 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy28/Android.bp
@@ -25,6 +25,8 @@
         "ctstestrunner-axt",
         "androidx.test.rules",
         "junit",
+        "permission-test-util-lib",
+        "compatibility-device-util-axt",
     ],
     libs: [
         "android.test.runner",
diff --git a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
index cf8981d..828c356 100644
--- a/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy28/src/android/app/notification/legacy28/cts/NotificationManager28Test.java
@@ -16,6 +16,10 @@
 
 package android.app.notification.legacy28.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
+
 import static junit.framework.TestCase.assertEquals;
 import static junit.framework.TestCase.assertNotNull;
 
@@ -25,12 +29,18 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -49,13 +59,26 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
-
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
         mNotificationManager.createNotificationChannel(new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
     }
 
+    @After
+    public void tearDown() throws Exception {
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
+    }
+
     @Test
     public void testPostFullScreenIntent_noPermission() {
         // No Full screen intent permission; but full screen intent should still be allowed
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
index 0d01e20..ca03efa 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
+++ b/tests/tests/notificationlegacy/notificationlegacy29/Android.bp
@@ -24,6 +24,8 @@
         "ctstestrunner-axt",
         "androidx.test.rules",
         "junit",
+        "permission-test-util-lib",
+        "compatibility-device-util-axt",
     ],
     libs: [
         "android.test.runner",
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 c02c091..bd470a7 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,9 @@
 
 package android.app.notification.legacy29.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
 import static android.service.notification.NotificationAssistantService.FEEDBACK_RATING;
 
 import static junit.framework.Assert.assertEquals;
@@ -41,7 +44,10 @@
 import android.content.pm.PackageManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.SystemClock;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.provider.Telephony;
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationAssistantService;
@@ -51,6 +57,8 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import junit.framework.Assert;
 
 import org.junit.After;
@@ -85,9 +93,10 @@
     }
 
     @Before
-    public void setUp() throws IOException {
+    public void setUp() throws Exception {
         mUi = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         mContext = InstrumentationRegistry.getContext();
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
         mNotificationManager.createNotificationChannel(new NotificationChannel(
@@ -97,7 +106,16 @@
     }
 
     @After
-    public void tearDown() throws IOException {
+    public void tearDown() throws Exception {
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
         if (mNotificationListenerService != null) mNotificationListenerService.resetData();
 
         toggleListenerAccess(false);
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
index c164656..0a2021b 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/NotificationManager29Test.java
@@ -16,6 +16,9 @@
 
 package android.app.notification.legacy29.cts;
 
+import static android.Manifest.permission.POST_NOTIFICATIONS;
+import static android.Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL;
+import static android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS;
 import static android.app.NotificationManager.Policy.CONVERSATION_SENDERS_ANYONE;
 import static android.app.NotificationManager.Policy.PRIORITY_CATEGORY_CONVERSATIONS;
 
@@ -33,14 +36,20 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
+import android.permission.PermissionManager;
+import android.permission.cts.PermissionUtils;
 import android.service.notification.StatusBarNotification;
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import junit.framework.Assert;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -63,13 +72,26 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
-
+        PermissionUtils.grantPermission(mContext.getPackageName(), POST_NOTIFICATIONS);
         mNotificationManager = (NotificationManager) mContext.getSystemService(
                 Context.NOTIFICATION_SERVICE);
         mNotificationManager.createNotificationChannel(new NotificationChannel(
                 NOTIFICATION_CHANNEL_ID, "name", NotificationManager.IMPORTANCE_DEFAULT));
     }
 
+    @After
+    public void tearDown() throws Exception {
+        // Use test API to prevent PermissionManager from killing the test process when revoking
+        // permission.
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mContext.getSystemService(PermissionManager.class)
+                        .revokePostNotificationPermissionWithoutKillForTest(
+                                mContext.getPackageName(),
+                                Process.myUserHandle().getIdentifier()),
+                REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
+                REVOKE_RUNTIME_PERMISSIONS);
+    }
+
     private void toggleNotificationPolicyAccess(String packageName,
             Instrumentation instrumentation, boolean on) throws IOException {
 
diff --git a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
index 7311878..308c757 100644
--- a/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
+++ b/tests/tests/os/src/android/os/cts/AppHibernationIntegrationTest.kt
@@ -26,6 +26,7 @@
 import android.content.pm.PackageManager
 import android.net.Uri
 import android.os.Build
+import android.permission.PermissionControllerManager
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
 import android.provider.Settings
@@ -43,6 +44,7 @@
 import com.android.compatibility.common.util.SystemUtil
 import com.android.compatibility.common.util.SystemUtil.eventually
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.UiAutomatorUtils
 import org.hamcrest.CoreMatchers
 import org.hamcrest.Matchers
@@ -55,6 +57,8 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
 
 /**
  * Integration test for app hibernation.
@@ -65,6 +69,7 @@
     companion object {
         const val LOG_TAG = "AppHibernationIntegrationTest"
         const val WAIT_TIME_MS = 1000L
+        const val TIMEOUT_TIME_MS = 5000L
         const val MAX_SCROLL_ATTEMPTS = 3
         const val TEST_UNUSED_THRESHOLD = 1L
 
@@ -74,6 +79,7 @@
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
 
     private lateinit var packageManager: PackageManager
+    private lateinit var permissionControllerManager: PermissionControllerManager
 
     @get:Rule
     val disableAnimationRule = DisableAnimationRule()
@@ -84,6 +90,8 @@
     @Before
     fun setup() {
         packageManager = context.packageManager
+        permissionControllerManager =
+            context.getSystemService(PermissionControllerManager::class.java)!!
 
         // Collapse notifications
         assertThat(
@@ -160,6 +168,41 @@
         }
     }
 
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    fun testUnusedAppCount() {
+        withUnusedThresholdMs(TEST_UNUSED_THRESHOLD) {
+            withApp(APK_PATH_S_APP, APK_PACKAGE_NAME_S_APP) {
+                // Use app
+                startApp(APK_PACKAGE_NAME_S_APP)
+                leaveApp(APK_PACKAGE_NAME_S_APP)
+                killApp(APK_PACKAGE_NAME_S_APP)
+
+                // Wait for the unused threshold time to pass
+                Thread.sleep(TEST_UNUSED_THRESHOLD)
+
+                // Run job
+                runAppHibernationJob(context, LOG_TAG)
+
+                // Verify unused app count pulled correctly
+                val countDownLatch = CountDownLatch(1)
+                var unusedAppCount = -1
+                runWithShellPermissionIdentity {
+                    permissionControllerManager.getUnusedAppCount({ r -> r.run() },
+                        { res ->
+                            unusedAppCount = res
+                            countDownLatch.countDown()
+                        })
+
+                    assertTrue("Timed out waiting for unused app count",
+                        countDownLatch.await(TIMEOUT_TIME_MS, TimeUnit.MILLISECONDS))
+                    assertTrue("Expected non-zero unused app count but is $unusedAppCount",
+                        unusedAppCount > 0)
+                }
+            }
+        }
+    }
+
     @AppModeFull(reason = "Uses application details settings")
     @Test
     fun testAppInfo_RemovePermissionsAndFreeUpSpaceToggleExists() {
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index 070cc44..2430c6b 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -28,8 +28,8 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.InputStreamReader;
-import java.util.HashSet;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
diff --git a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
index b0307f1..c10120e 100644
--- a/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
+++ b/tests/tests/os/src/android/os/cts/CompanionDeviceManagerTest.kt
@@ -32,7 +32,7 @@
 import android.util.Size
 import android.util.SizeF
 import android.util.SparseArray
-import android.widget.TextView
+import android.widget.ListView
 import androidx.test.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import androidx.test.uiautomator.By
@@ -48,7 +48,6 @@
 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
 import com.android.compatibility.common.util.ThrowingSupplier
 import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
-import com.android.compatibility.common.util.children
 import com.android.compatibility.common.util.click
 import java.io.Serializable
 import org.hamcrest.CoreMatchers.containsString
@@ -77,6 +76,12 @@
         const val SHELL_PACKAGE_NAME = "com.android.shell"
         const val TEST_APP_PACKAGE_NAME = "android.os.cts.companiontestapp"
         const val TEST_APP_APK_LOCATION = "/data/local/tmp/cts/os/CtsCompanionTestApp.apk"
+        const val CDM_UI_PACKAGE_NAME = "com.android.companiondevicemanager"
+
+        val DEVICE_LIST_ITEM_SELECTOR: BySelector = By.res(CDM_UI_PACKAGE_NAME, "list_item_device")
+        val DEVICE_LIST_SELECTOR: BySelector = By.pkg(CDM_UI_PACKAGE_NAME)
+                .clazz(ListView::class.java.name)
+                .hasChild(DEVICE_LIST_ITEM_SELECTOR)
     }
 
     private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
@@ -172,16 +177,12 @@
         uiDevice.waitForIdle()
 
         click("Watch")
-        val device = getEventually({
-            click("Associate")
-            waitFindNode(hasIdThat(containsString("device_list")),
-                    failMsg = "Test requires a discoverable bluetooth device nearby",
-                    timeoutMs = 9_000)
-                    .children
-                    .find { it.className == TextView::class.java.name }
-                    .assertNotNull { "Empty device list" }
-        }, 90_000)
-        device!!.click()
+        click("Associate")
+
+        uiDevice.wait(Until.findObject(DEVICE_LIST_SELECTOR), 20_000)
+                ?.findObject(DEVICE_LIST_ITEM_SELECTOR)
+                ?.click()
+                ?: throw AssertionError("Empty device list")
 
         eventually {
             assertThat(getAssociatedDevices(TEST_APP_PACKAGE_NAME), not(empty()))
@@ -210,17 +211,12 @@
         uiDevice.waitAndFind(By.desc("name filter")).text = ""
         uiDevice.waitForIdle()
 
-        val deviceForAssociation = getEventually({
-            click("Associate")
-            waitFindNode(hasIdThat(containsString("device_list")),
-                    failMsg = "Test requires a discoverable bluetooth device nearby",
-                    timeoutMs = 5_000)
-                    .children
-                    .find { it.className == TextView::class.java.name }
-                    .assertNotNull { "Empty device list" }
-        }, 60_000)
+        click("Associate")
 
-        deviceForAssociation!!.click()
+        uiDevice.wait(Until.findObject(DEVICE_LIST_SELECTOR), 20_000)
+                ?.findObject(DEVICE_LIST_ITEM_SELECTOR)
+                ?.click()
+                ?: throw AssertionError("Empty device list")
 
         waitForIdle()
 
diff --git a/tests/tests/os/src/android/os/cts/LocaleListTest.java b/tests/tests/os/src/android/os/cts/LocaleListTest.java
index 3d05332..84cff45 100644
--- a/tests/tests/os/src/android/os/cts/LocaleListTest.java
+++ b/tests/tests/os/src/android/os/cts/LocaleListTest.java
@@ -530,4 +530,40 @@
         assertFalse(LocaleList.isPseudoLocale(ULocale.forLanguageTag("fr-CA")));
         assertFalse(LocaleList.isPseudoLocale((ULocale) null));
     }
+
+    public void testMatchesLanguageAndScript() {
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("fr-Latn-FR"),
+                Locale.forLanguageTag("fr-Latn")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hans-CN"),
+                Locale.forLanguageTag("zh-Hans")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hant-TW"),
+                Locale.forLanguageTag("zh-Hant")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+                Locale.forLanguageTag("en-US")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+                Locale.forLanguageTag("en-CA")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("ar-NA"),
+                Locale.forLanguageTag("ar-ZA")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+                Locale.forLanguageTag("zh")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+                Locale.forLanguageTag("zh-Hans")));
+        assertTrue(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+                Locale.forLanguageTag("zh-Hant")));
+
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-Hant-TW"),
+                Locale.forLanguageTag("zh-Hans")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-XA"),
+                Locale.forLanguageTag("en-US")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("ar-YE"),
+                Locale.forLanguageTag("ar-XB")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("en-US"),
+                Locale.forLanguageTag("zh-TW")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+                Locale.forLanguageTag("zh")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-CN"),
+                Locale.forLanguageTag("zh-Hant")));
+        assertFalse(LocaleList.matchesLanguageAndScript(Locale.forLanguageTag("zh-TW"),
+                Locale.forLanguageTag("zh-Hans")));
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/ParcelTest.java b/tests/tests/os/src/android/os/cts/ParcelTest.java
index f4b1a06..f567daa 100644
--- a/tests/tests/os/src/android/os/cts/ParcelTest.java
+++ b/tests/tests/os/src/android/os/cts/ParcelTest.java
@@ -704,6 +704,43 @@
         p.recycle();
     }
 
+    public void testWriteBlob() {
+        Parcel p;
+
+        byte[] shortBytes = {(byte) 21};
+        // Create a byte array with 70 KiB to make sure it is large enough to be saved into Android
+        // Shared Memory. The native blob inplace limit is 16 KiB. Also make it larger than the
+        // IBinder.MAX_IPC_SIZE which is 64 KiB.
+        byte[] largeBytes = new byte[70 * 1024];
+        for (int i = 0; i < largeBytes.length; i++) {
+            largeBytes[i] = (byte) (i / Byte.MAX_VALUE);
+        }
+        // test write null
+        p = Parcel.obtain();
+        p.writeBlob(null, 0, 2);
+        p.setDataPosition(0);
+        byte[] outputBytes = p.readBlob();
+        assertNull(outputBytes);
+        p.recycle();
+
+        // test write short bytes
+        p = Parcel.obtain();
+        p.writeBlob(shortBytes, 0, 1);
+        p.setDataPosition(0);
+        assertEquals(shortBytes[0], p.readBlob()[0]);
+        p.recycle();
+
+        // test write large bytes
+        p = Parcel.obtain();
+        p.writeBlob(largeBytes, 0, largeBytes.length);
+        p.setDataPosition(0);
+        outputBytes = p.readBlob();
+        for (int i = 0; i < largeBytes.length; i++) {
+            assertEquals(largeBytes[i], outputBytes[i]);
+        }
+        p.recycle();
+    }
+
     public void testWriteByteArray() {
         Parcel p;
 
diff --git a/tests/tests/os/src/android/os/cts/ProcessTest.java b/tests/tests/os/src/android/os/cts/ProcessTest.java
index 2ca0fd0..909f147 100644
--- a/tests/tests/os/src/android/os/cts/ProcessTest.java
+++ b/tests/tests/os/src/android/os/cts/ProcessTest.java
@@ -25,6 +25,11 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+/**
+ * CTS for {@link android.os.Process}.
+ *
+ * We have more test in cts/tests/process/ too.
+ */
 public class ProcessTest extends AndroidTestCase {
 
     public static final int THREAD_PRIORITY_HIGHEST = -20;
diff --git a/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java b/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java
new file mode 100644
index 0000000..9668a61
--- /dev/null
+++ b/tests/tests/os/src/android/os/cts/SharedMemoryFileDescriptorTest.java
@@ -0,0 +1,147 @@
+/*
+ * 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.os.cts;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.SharedMemory;
+import android.system.OsConstants;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.ByteBuffer;
+
+@RunWith(AndroidJUnit4.class)
+public final class SharedMemoryFileDescriptorTest {
+
+    private static final String TAG = SharedMemoryFileDescriptorTest.class.getSimpleName();
+
+    @Test
+    public void testCreation() throws Exception {
+        // setup
+        int memSize = 32 * 1024;
+        int bufSize = memSize / 4;
+        SharedMemory sharedMem1 = SharedMemory.create(/* name= */null, memSize);
+        try (ByteBufferSession buffer1 =
+                new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+            buffer1.fillSequentialPattern();
+        }
+
+        Parcel p = Parcel.obtain();
+        sharedMem1.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ParcelFileDescriptor fileDescriptor = p.readFileDescriptor();
+
+        // execution
+        SharedMemory sharedMem2 = SharedMemory.fromFileDescriptor(fileDescriptor);
+
+        // assertion
+        assertFalse(fileDescriptor.getFileDescriptor().valid());
+        try (ByteBufferSession buffer2 =
+                new ByteBufferSession(sharedMem2, /* offset= */ 0, bufSize)) {
+            assertTrue(buffer2.checkSequentialPattern());
+        }
+    }
+
+    @Test
+    public void testDuplication() throws Exception {
+        // setup
+        int memSize = 32 * 1024;
+        int bufSize = memSize / 4;
+        SharedMemory sharedMem1 = SharedMemory.create(/* name= */ null, memSize);
+        try (ByteBufferSession buffer1 =
+                new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+            buffer1.fillSequentialPattern();
+        }
+
+        Parcel p = Parcel.obtain();
+        sharedMem1.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        ParcelFileDescriptor fileDescriptor = p.readFileDescriptor();
+        ParcelFileDescriptor dupFileDescriptor = fileDescriptor.dup();
+
+        // execution
+        SharedMemory sharedMem2 = SharedMemory.fromFileDescriptor(dupFileDescriptor);
+
+        // assertion
+        assertTrue(fileDescriptor.getFileDescriptor().valid());
+        try (ByteBufferSession buffer2 =
+                new ByteBufferSession(sharedMem2, /* offset= */ 0, bufSize)) {
+            assertTrue(buffer2.checkSequentialPattern());
+            buffer2.clearMemory();
+        }
+        try (ByteBufferSession buffer1 =
+                new ByteBufferSession(sharedMem1, /* offset= */ 0, bufSize)) {
+            // Clearing the shared RAM via sharedMem2 should be visible to sharedMem1
+            assertTrue(buffer1.hasCleanMemory());
+        }
+    }
+
+    private static final class ByteBufferSession implements AutoCloseable {
+        private final ByteBuffer mBuf;
+        private final int mSize;
+
+        ByteBufferSession(SharedMemory mem, int offset, int length)
+                throws Exception {
+            mBuf = mem.map(OsConstants.PROT_READ | OsConstants.PROT_WRITE, offset, length);
+            mSize = length;
+        }
+
+        @Override
+        public void close() {
+            SharedMemory.unmap(mBuf);
+        }
+
+        public void clearMemory() {
+            mBuf.clear();
+            for (int i = 0; i < mSize; i++) {
+                mBuf.put((byte) 0);
+            }
+        }
+
+        public boolean hasCleanMemory() throws Exception {
+            for (int i = 0; i < mSize; i++) {
+                if (mBuf.get() != 0) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public void fillSequentialPattern() {
+            for (int fillPos = 0; fillPos < mSize; fillPos++) {
+                mBuf.put(fillPos, (byte) fillPos);
+            }
+        }
+
+        public boolean checkSequentialPattern() {
+            for (int fillPos = 0; fillPos < mSize; fillPos++) {
+                if (mBuf.get() != (byte) fillPos) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
diff --git a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
index 966780b..9d64927 100644
--- a/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationAttributesTest.java
@@ -21,7 +21,6 @@
 
 import android.media.AudioAttributes;
 import android.os.VibrationAttributes;
-import android.os.VibrationEffect;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,35 +28,73 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class VibrationAttributesTest {
-    private static final int TEST_USAGE = AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
+    private static final Map<Integer, Integer> AUDIO_TO_VIBRATION_USAGE_MAP;
+    static {
+        Map<Integer, Integer> map = new HashMap<>();
+        map.put(AudioAttributes.USAGE_ALARM, VibrationAttributes.USAGE_ALARM);
+        map.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+                VibrationAttributes.USAGE_ACCESSIBILITY);
+        map.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, VibrationAttributes.USAGE_TOUCH);
+        map.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+                VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        map.put(AudioAttributes.USAGE_ASSISTANT, VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        map.put(AudioAttributes.USAGE_GAME, VibrationAttributes.USAGE_MEDIA);
+        map.put(AudioAttributes.USAGE_MEDIA, VibrationAttributes.USAGE_MEDIA);
+        map.put(AudioAttributes.USAGE_NOTIFICATION, VibrationAttributes.USAGE_NOTIFICATION);
+        map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
+                VibrationAttributes.USAGE_NOTIFICATION);
+        map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
+                VibrationAttributes.USAGE_NOTIFICATION);
+        map.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
+                VibrationAttributes.USAGE_NOTIFICATION);
+        map.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, VibrationAttributes.USAGE_NOTIFICATION);
+        map.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, VibrationAttributes.USAGE_RINGTONE);
+        map.put(AudioAttributes.USAGE_UNKNOWN, VibrationAttributes.USAGE_UNKNOWN);
+        map.put(AudioAttributes.USAGE_VOICE_COMMUNICATION,
+                VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        map.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+                VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
+        AUDIO_TO_VIBRATION_USAGE_MAP = map;
+    }
 
-    private static final int TEST_AMPLITUDE = 100;
-    private static final long TEST_TIMING_LONG = 5000;
-    private static final long TEST_TIMING_SHORT = 4999;
-    private static final long[] TEST_TIMINGS_LONG = new long[] { 100, 100, 4800 };
-    private static final long[] TEST_TIMINGS_SHORT = new long[] { 100, 100, 4799 };
-
-
-    private static final VibrationEffect TEST_ONE_SHOT_LONG =
-            VibrationEffect.createOneShot(TEST_TIMING_LONG, TEST_AMPLITUDE);
-    private static final VibrationEffect TEST_ONE_SHOT_SHORT =
-            VibrationEffect.createOneShot(TEST_TIMING_SHORT, TEST_AMPLITUDE);
-    private static final VibrationEffect TEST_WAVEFORM_LONG =
-            VibrationEffect.createWaveform(TEST_TIMINGS_LONG, -1);
-    private static final VibrationEffect TEST_WAVEFORM_SHORT =
-            VibrationEffect.createWaveform(TEST_TIMINGS_SHORT, -1);
-    private static final VibrationEffect TEST_PREBAKED =
-            VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
+    private static final Map<Integer, Integer> VIBRATION_TO_AUDIO_USAGE_MAP;
+    static {
+        Map<Integer, Integer> map = new HashMap<>();
+        map.put(VibrationAttributes.USAGE_ALARM, AudioAttributes.USAGE_ALARM);
+        map.put(VibrationAttributes.USAGE_ACCESSIBILITY,
+                AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+        map.put(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
+                AudioAttributes.USAGE_VOICE_COMMUNICATION);
+        map.put(VibrationAttributes.USAGE_HARDWARE_FEEDBACK, AudioAttributes.USAGE_UNKNOWN);
+        map.put(VibrationAttributes.USAGE_MEDIA, AudioAttributes.USAGE_MEDIA);
+        map.put(VibrationAttributes.USAGE_NOTIFICATION, AudioAttributes.USAGE_NOTIFICATION);
+        map.put(VibrationAttributes.USAGE_PHYSICAL_EMULATION, AudioAttributes.USAGE_UNKNOWN);
+        map.put(VibrationAttributes.USAGE_RINGTONE, AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+        map.put(VibrationAttributes.USAGE_TOUCH, AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+        VIBRATION_TO_AUDIO_USAGE_MAP = map;
+    }
 
     @Test
-    public void testCreate() {
-        AudioAttributes tmp = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ALARM)
-                .build();
-        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+    public void testCreateFromVibrationAttributes() {
+        VibrationAttributes tmp = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_TOUCH);
+        VibrationAttributes attr = new VibrationAttributes.Builder(tmp).build();
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_TOUCH);
+        assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_FEEDBACK);
+        assertEquals(attr.getFlags(), 0);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+    }
+
+    @Test
+    public void testCreateFromAudioAttributes() {
+        AudioAttributes audioAttributes = createAudioAttributes(AudioAttributes.USAGE_ALARM);
+        VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
         assertEquals(attr.getUsage(), VibrationAttributes.USAGE_ALARM);
         assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
         assertEquals(attr.getFlags(), 0);
@@ -65,90 +102,91 @@
     }
 
     @Test
+    public void testCreateForUsage() {
+        VibrationAttributes attr = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_RINGTONE);
+        assertEquals(attr.getUsage(), VibrationAttributes.USAGE_RINGTONE);
+        assertEquals(attr.getUsageClass(), VibrationAttributes.USAGE_CLASS_ALARM);
+        assertEquals(attr.getFlags(), 0);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION_RINGTONE);
+    }
+
+    @Test
     public void testGetAudioUsageReturnOriginalUsage() {
-        AudioAttributes tmp = new AudioAttributes.Builder()
-                .setUsage(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY)
-                .build();
-        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
+        VibrationAttributes attr = createForAudioUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION);
         assertEquals(attr.getUsage(), VibrationAttributes.USAGE_COMMUNICATION_REQUEST);
-        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+        assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_VOICE_COMMUNICATION);
     }
 
     @Test
     public void testGetAudioUsageUnknownReturnsBasedOnVibrationUsage() {
-        VibrationAttributes attr = new VibrationAttributes.Builder()
-                .setUsage(VibrationAttributes.USAGE_NOTIFICATION).build();
+        VibrationAttributes attr = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_NOTIFICATION);
         assertEquals(attr.getUsage(), VibrationAttributes.USAGE_NOTIFICATION);
         assertEquals(attr.getAudioUsage(), AudioAttributes.USAGE_NOTIFICATION);
     }
 
     @Test
+    public void testAudioToVibrationUsageMapping() {
+        for (Map.Entry<Integer, Integer> entry : AUDIO_TO_VIBRATION_USAGE_MAP.entrySet()) {
+            assertEquals(entry.getValue().intValue(), new VibrationAttributes.Builder(
+                    createForAudioUsage(entry.getKey())).build().getUsage());
+        }
+    }
+
+    @Test
+    public void testVibrationToAudioUsageMapping() {
+        for (Map.Entry<Integer, Integer> entry : VIBRATION_TO_AUDIO_USAGE_MAP.entrySet()) {
+            assertEquals(entry.getValue().intValue(),
+                    new VibrationAttributes.Builder()
+                            .setUsage(entry.getKey())
+                            .build()
+                            .getAudioUsage());
+        }
+    }
+
+    @Test
     public void testEquals() {
-        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
-        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).build();
+        AudioAttributes audioAttributes = createAudioAttributes(
+                AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY);
+        VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
+        VibrationAttributes attr2 = new VibrationAttributes.Builder(audioAttributes).build();
         assertEquals(attr, attr2);
     }
 
     @Test
     public void testNotEqualsDifferentAudioUsage() {
-        AudioAttributes tmp = createAudioAttributes(
-                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT);
-        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        AudioAttributes tmp2 = createAudioAttributes(
-                AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED);
-        VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp2, null).build();
+        VibrationAttributes attr = createForAudioUsage(AudioAttributes.USAGE_NOTIFICATION);
+        VibrationAttributes attr2 = createForAudioUsage(AudioAttributes.USAGE_NOTIFICATION_EVENT);
         assertEquals(attr.getUsage(), attr2.getUsage());
         assertNotEquals(attr, attr2);
     }
 
     @Test
     public void testNotEqualsDifferentVibrationUsage() {
-        VibrationAttributes attr = new VibrationAttributes.Builder()
-                .setUsage(VibrationAttributes.USAGE_TOUCH)
-                .build();
-        VibrationAttributes attr2 = new VibrationAttributes.Builder()
-                .setUsage(VibrationAttributes.USAGE_NOTIFICATION)
-                .build();
+        VibrationAttributes attr = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_TOUCH);
+        VibrationAttributes attr2 = VibrationAttributes.createForUsage(
+                VibrationAttributes.USAGE_NOTIFICATION);
         assertNotEquals(attr, attr2);
     }
 
     @Test
     public void testNotEqualsDifferentFlags() {
-        AudioAttributes tmp = createAudioAttributes(TEST_USAGE);
-        VibrationAttributes attr = new VibrationAttributes.Builder(tmp, null).build();
-        VibrationAttributes attr2 = new VibrationAttributes.Builder(tmp, null).setFlags(1, 1)
+        AudioAttributes audioAttributes = createAudioAttributes(
+                AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+        VibrationAttributes attr = new VibrationAttributes.Builder(audioAttributes).build();
+        VibrationAttributes attr2 = new VibrationAttributes.Builder(audioAttributes).setFlags(1, 1)
                 .build();
         assertNotEquals(attr, attr2);
     }
 
-    @Test
-    public void testHeuristics() {
-        AudioAttributes tmp = createAudioAttributes(AudioAttributes.USAGE_UNKNOWN);
-        VibrationAttributes oneShotLong =
-            new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_LONG).build();
-        VibrationAttributes oneShotShort =
-            new VibrationAttributes.Builder(tmp, TEST_ONE_SHOT_SHORT).build();
-        VibrationAttributes waveformLong =
-            new VibrationAttributes.Builder(tmp, TEST_WAVEFORM_LONG).build();
-        VibrationAttributes waveformShort =
-            new VibrationAttributes.Builder(tmp, TEST_WAVEFORM_SHORT).build();
-        VibrationAttributes prebaked =
-            new VibrationAttributes.Builder(tmp, TEST_PREBAKED).build();
-        assertEquals(oneShotShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
-        assertEquals(oneShotShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
-        assertEquals(waveformShort.getUsage(), VibrationAttributes.USAGE_TOUCH);
-        assertEquals(waveformShort.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
-        assertEquals(oneShotLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
-        assertEquals(oneShotLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
-        assertEquals(waveformLong.getUsage(), VibrationAttributes.USAGE_UNKNOWN);
-        assertEquals(waveformLong.getAudioUsage(), AudioAttributes.USAGE_UNKNOWN);
-        assertEquals(prebaked.getUsage(), VibrationAttributes.USAGE_TOUCH);
-        assertEquals(prebaked.getAudioUsage(), AudioAttributes.USAGE_ASSISTANCE_SONIFICATION);
+    private static AudioAttributes createAudioAttributes(int audioUsage) {
+        return new AudioAttributes.Builder().setUsage(audioUsage).build();
     }
 
-    private static AudioAttributes createAudioAttributes(int usage) {
-        return new AudioAttributes.Builder().setUsage(usage).build();
+    private static VibrationAttributes createForAudioUsage(int audioUsage) {
+        return new VibrationAttributes.Builder(createAudioAttributes(audioUsage)).build();
     }
 }
 
diff --git a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
index 0b0f5e4..e34ae5d 100644
--- a/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
+++ b/tests/tests/os/src/android/os/cts/VibrationEffectTest.java
@@ -18,7 +18,6 @@
 
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
@@ -63,9 +62,9 @@
     private static final VibrationEffect TEST_WAVEFORM_BUILT =
             VibrationEffect.startWaveform()
                     .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
-                    .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                    .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
                     .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
-                    .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                    .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
                     .build();
     private static final VibrationEffect TEST_PREBAKED =
             VibrationEffect.get(VibrationEffect.EFFECT_CLICK, true);
@@ -547,9 +546,9 @@
 
         VibrationEffect effect = VibrationEffect.startWaveform()
                 .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
-                .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
                 .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
-                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
                 .build();
 
         assertArrayEquals(new long[]{10, 20, 100, 200}, getTimings(effect));
@@ -559,24 +558,24 @@
 
         assertStepSegment(effect, 1);
         assertAmplitude(0.8f, effect, 1);
-        assertFrequency(-1f, effect, 1);
+        assertFrequency(100f, effect, 1);
 
         assertRampSegment(effect, 2);
         assertAmplitude(1f, effect, 2);
-        assertFrequency(-1f, effect, 2);
+        assertFrequency(100f, effect, 2);
 
         assertRampSegment(effect, 3);
         assertAmplitude(0.2f, effect, 3);
-        assertFrequency(1f, effect, 3);
+        assertFrequency(200f, effect, 3);
     }
 
     @Test
     public void testStartWaveformEquals() {
         VibrationEffect other = VibrationEffect.startWaveform()
                 .addStep(/* amplitude= */ 0.5f, /* duration= */ 10)
-                .addStep(/* amplitude= */ 0.8f, /* frequency= */ -1f, /* duration= */ 20)
+                .addStep(/* amplitude= */ 0.8f, /* frequency= */ 100f, /* duration= */ 20)
                 .addRamp(/* amplitude= */ 1f, /* duration= */ 100)
-                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 1f, /* duration= */ 200)
+                .addRamp(/* amplitude= */ 0.2f, /* frequency= */ 200f, /* duration= */ 200)
                 .build();
         assertEquals(TEST_WAVEFORM_BUILT, other);
         assertEquals(TEST_WAVEFORM_BUILT.hashCode(), other.hashCode());
@@ -647,10 +646,10 @@
     @Test
     public void testStartWaveformNotEqualsDifferentFrequency() {
         VibrationEffect first = VibrationEffect.startWaveform()
-                .addStep(/* amplitude= */ 0.5f, /* frequency= */ 0.5f, /* duration= */ 10)
+                .addStep(/* amplitude= */ 0.5f, /* frequency= */ 1f, /* duration= */ 10)
                 .build();
         VibrationEffect second = VibrationEffect.startWaveform()
-                .addStep(/* amplitude= */ 0.5f, /* frequency= */ -1f, /* duration= */ 10)
+                .addStep(/* amplitude= */ 0.5f, /* frequency= */ 50f, /* duration= */ 10)
                 .build();
         assertNotEquals(first, second);
     }
@@ -735,11 +734,12 @@
         assertTrue(index < composed.getSegments().size());
         VibrationEffectSegment segment = composed.getSegments().get(index);
         if (segment instanceof StepSegment) {
-            assertEquals(expected, ((StepSegment) composed.getSegments().get(index)).getFrequency(),
+            assertEquals(expected,
+                    ((StepSegment) composed.getSegments().get(index)).getFrequencyHz(),
                     TEST_TOLERANCE);
         } else if (segment instanceof RampSegment) {
             assertEquals(expected,
-                    ((RampSegment) composed.getSegments().get(index)).getEndFrequency(),
+                    ((RampSegment) composed.getSegments().get(index)).getEndFrequencyHz(),
                     TEST_TOLERANCE);
         } else {
             fail("Expected a step or ramp segment at index " + index + " of " + effect);
diff --git a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
index 398938bb..2ddecab 100644
--- a/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorManagerTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.timeout;
@@ -68,15 +69,20 @@
     @Rule
     public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
-    private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
+    private static final long CALLBACK_TIMEOUT_MILLIS = 5_000;
     private static final VibrationAttributes VIBRATION_ATTRIBUTES =
             new VibrationAttributes.Builder()
                     .setUsage(VibrationAttributes.USAGE_TOUCH)
                     .build();
 
-    private VibratorManager mVibratorManager;
+    /**
+     * These listeners are used for test helper methods like asserting it starts/stops vibrating.
+     * It's not strongly required that the interactions with these mocks are validated by all tests.
+     */
     private final SparseArray<OnVibratorStateChangedListener> mStateListeners = new SparseArray<>();
 
+    private VibratorManager mVibratorManager;
+
     @Before
     public void setUp() {
         mVibratorManager =
@@ -87,13 +93,34 @@
             OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
             mVibratorManager.getVibrator(vibratorId).addVibratorStateListener(listener);
             mStateListeners.put(vibratorId, listener);
-            reset(listener);
+            // Adding a listener to the Vibrator should trigger the callback once with the current
+            // vibrator state, so reset mocks to clear it for tests.
+            assertVibratorState(false);
+            clearInvocations(listener);
         }
     }
 
     @After
     public void cleanUp() {
+        // Clearing invocations so we can use these listeners to wait for the vibrator to
+        // asynchronously cancel the ongoing vibration, if any was left pending by a test.
+        for (int i = 0; i < mStateListeners.size(); i++) {
+            clearInvocations(mStateListeners.valueAt(i));
+        }
         mVibratorManager.cancel();
+
+        for (int i = 0; i < mStateListeners.size(); i++) {
+            int vibratorId = mStateListeners.keyAt(i);
+
+            // Wait for cancel to take effect, if device is still vibrating.
+            if (mVibratorManager.getVibrator(vibratorId).isVibrating()) {
+                assertStopsVibrating(vibratorId);
+            }
+
+            // Remove all listeners added by the tests.
+            mVibratorManager.getVibrator(vibratorId).removeVibratorStateListener(
+                    mStateListeners.valueAt(i));
+        }
     }
 
     @Test
@@ -108,20 +135,26 @@
 
     @LargeTest
     @Test
-    public void testVibrateOneShot() {
+    public void testVibrateOneShotStartsAndFinishesVibration() {
         VibrationEffect oneShot =
                 VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
         assertStartsThenStopsVibrating(300);
+    }
 
-        oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
+    @Test
+    public void testVibrateOneShotMaxAmplitude() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(500, 255 /* Max amplitude */);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot));
         assertStartsVibrating();
 
         mVibratorManager.cancel();
         assertStopsVibrating();
+    }
 
-        oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
+    @Test
+    public void testVibrateOneShotMinAmplitude() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(100, 1 /* Min amplitude */);
         mVibratorManager.vibrate(CombinedVibration.createParallel(oneShot),
                 VIBRATION_ATTRIBUTES);
         assertStartsVibrating();
@@ -129,17 +162,29 @@
 
     @LargeTest
     @Test
-    public void testVibrateWaveform() {
+    public void testVibrateWaveformStartsAndFinishesVibration() {
         final long[] timings = new long[]{100, 200, 300, 400, 500};
         final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
         VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
         mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
         assertStartsThenStopsVibrating(1500);
+    }
 
-        waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+    @LargeTest
+    @Test
+    public void testVibrateWaveformRepeats() {
+        final long[] timings = new long[]{100, 200, 300, 400, 500};
+        final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
+        VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
         mVibratorManager.vibrate(CombinedVibration.createParallel(waveform));
         assertStartsVibrating();
 
+        SystemClock.sleep(2000);
+        int[] vibratorIds = mVibratorManager.getVibratorIds();
+        for (int vibratorId : vibratorIds) {
+            assertTrue(mVibratorManager.getVibrator(vibratorId).isVibrating());
+        }
+
         mVibratorManager.cancel();
         assertStopsVibrating();
     }
@@ -227,8 +272,7 @@
 
     private void assertStartsThenStopsVibrating(long duration) {
         for (int i = 0; i < mStateListeners.size(); i++) {
-            verify(mStateListeners.valueAt(i), timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
-                    .onVibratorStateChanged(true);
+            assertVibratorState(mStateListeners.keyAt(i), true);
         }
         SystemClock.sleep(duration);
         assertVibratorState(false);
@@ -260,6 +304,5 @@
         OnVibratorStateChangedListener listener = mStateListeners.get(vibratorId);
         verify(listener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
                 .onVibratorStateChanged(eq(expected));
-        reset(listener);
     }
 }
diff --git a/tests/tests/os/src/android/os/cts/VibratorTest.java b/tests/tests/os/src/android/os/cts/VibratorTest.java
index cf2460c..7085ba5 100644
--- a/tests/tests/os/src/android/os/cts/VibratorTest.java
+++ b/tests/tests/os/src/android/os/cts/VibratorTest.java
@@ -18,18 +18,19 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.after;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 
 import android.media.AudioAttributes;
 import android.os.SystemClock;
+import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.Vibrator.OnVibratorStateChangedListener;
@@ -50,6 +51,8 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Executors;
 
 @RunWith(AndroidJUnit4.class)
@@ -72,6 +75,10 @@
                     .setUsage(AudioAttributes.USAGE_MEDIA)
                     .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                     .build();
+    private static final VibrationAttributes VIBRATION_ATTRIBUTES =
+            new VibrationAttributes.Builder()
+                    .setUsage(VibrationAttributes.USAGE_TOUCH)
+                    .build();
     private static final long CALLBACK_TIMEOUT_MILLIS = 5000;
     private static final int[] PREDEFINED_EFFECTS = new int[]{
             VibrationEffect.EFFECT_CLICK,
@@ -92,23 +99,74 @@
             VibrationEffect.Composition.PRIMITIVE_SPIN,
             VibrationEffect.Composition.PRIMITIVE_THUD,
     };
+    private static final int[] VIBRATION_USAGES = new int[] {
+            VibrationAttributes.USAGE_UNKNOWN,
+            VibrationAttributes.USAGE_ACCESSIBILITY,
+            VibrationAttributes.USAGE_ALARM,
+            VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
+            VibrationAttributes.USAGE_HARDWARE_FEEDBACK,
+            VibrationAttributes.USAGE_MEDIA,
+            VibrationAttributes.USAGE_NOTIFICATION,
+            VibrationAttributes.USAGE_PHYSICAL_EMULATION,
+            VibrationAttributes.USAGE_RINGTONE,
+            VibrationAttributes.USAGE_TOUCH,
+    };
 
-    private Vibrator mVibrator;
+
+    /**
+     * This listener is used for test helper methods like asserting it starts/stops vibrating.
+     * It's not strongly required that the interactions with this mock are validated by all tests.
+     */
     @Mock
     private OnVibratorStateChangedListener mStateListener;
 
+    private Vibrator mVibrator;
+    /** Keep track of any listener created to be added to the vibrator, for cleanup purposes. */
+    private List<OnVibratorStateChangedListener> mStateListenersCreated = new ArrayList<>();
+
     @Before
     public void setUp() {
         mVibrator = InstrumentationRegistry.getInstrumentation().getContext().getSystemService(
                 Vibrator.class);
 
         mVibrator.addVibratorStateListener(mStateListener);
-        reset(mStateListener);
+        // Adding a listener to the Vibrator should trigger the callback once with the current
+        // vibrator state, so reset mocks to clear it for tests.
+        assertVibratorState(false);
+        clearInvocations(mStateListener);
     }
 
     @After
     public void cleanUp() {
+        // Clearing invocations so we can use this listener to wait for the vibrator to
+        // asynchronously cancel the ongoing vibration, if any was left pending by a test.
+        clearInvocations(mStateListener);
         mVibrator.cancel();
+
+        // Wait for cancel to take effect, if device is still vibrating.
+        if (mVibrator.isVibrating()) {
+            assertStopsVibrating();
+        }
+
+        // Remove all listeners added by the tests.
+        mVibrator.removeVibratorStateListener(mStateListener);
+        for (OnVibratorStateChangedListener listener : mStateListenersCreated) {
+            mVibrator.removeVibratorStateListener(listener);
+        }
+    }
+
+    @Test
+    public void getDefaultVibrationIntensity_returnsValidIntensityForAllUsages() {
+        for (int usage : VIBRATION_USAGES) {
+            int intensity = mVibrator.getDefaultVibrationIntensity(usage);
+            assertTrue("Error for usage " + usage + " with default intensity " + intensity,
+                    (intensity >= Vibrator.VIBRATION_INTENSITY_OFF)
+                            && (intensity <= Vibrator.VIBRATION_INTENSITY_HIGH));
+        }
+
+        assertEquals("Invalid usage expected to have same default as USAGE_UNKNOWN",
+                mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_UNKNOWN),
+                mVibrator.getDefaultVibrationIntensity(-1));
     }
 
     @Test
@@ -156,34 +214,46 @@
 
     @LargeTest
     @Test
-    public void testVibrateOneShot() {
+    public void testVibrateOneShotStartsAndFinishesVibration() {
         VibrationEffect oneShot =
                 VibrationEffect.createOneShot(300, VibrationEffect.DEFAULT_AMPLITUDE);
         mVibrator.vibrate(oneShot);
         assertStartsThenStopsVibrating(300);
+    }
 
-        oneShot = VibrationEffect.createOneShot(10_000, 255 /* Max amplitude */);
+    @Test
+    public void testVibrateOneShotMaxAmplitude() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(10_000, 255 /* Max amplitude */);
         mVibrator.vibrate(oneShot);
         assertStartsVibrating();
 
         mVibrator.cancel();
         assertStopsVibrating();
+    }
 
-        oneShot = VibrationEffect.createOneShot(300, 1 /* Min amplitude */);
+    @Test
+    public void testVibrateOneShotMinAmplitude() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(300, 1 /* Min amplitude */);
         mVibrator.vibrate(oneShot, AUDIO_ATTRIBUTES);
         assertStartsVibrating();
     }
 
     @LargeTest
     @Test
-    public void testVibrateWaveform() {
-        final long[] timings = new long[] {100, 200, 300, 400, 500};
-        final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
+    public void testVibrateWaveformStartsAndFinishesVibration() {
+        final long[] timings = new long[]{100, 200, 300, 400, 500};
+        final int[] amplitudes = new int[]{64, 128, 255, 128, 64};
         VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, -1);
         mVibrator.vibrate(waveform);
         assertStartsThenStopsVibrating(1500);
+    }
 
-        waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
+    @LargeTest
+    @Test
+    public void testVibrateWaveformRepeats() {
+        final long[] timings = new long[] {100, 200, 300, 400, 500};
+        final int[] amplitudes = new int[] {64, 128, 255, 128, 64};
+        VibrationEffect waveform = VibrationEffect.createWaveform(timings, amplitudes, 0);
         mVibrator.vibrate(waveform, AUDIO_ATTRIBUTES);
         assertStartsVibrating();
 
@@ -221,6 +291,12 @@
     }
 
     @Test
+    public void testVibrateWithAttributes() {
+        mVibrator.vibrate(VibrationEffect.createOneShot(10, 10), VIBRATION_ATTRIBUTES);
+        assertStartsVibrating();
+    }
+
+    @Test
     public void testGetId() {
         // The system vibrator should not be mapped to any physical vibrator and use a default id.
         assertEquals(-1, mVibrator.getId());
@@ -326,8 +402,8 @@
             return;
         }
 
-        OnVibratorStateChangedListener listener1 = mock(OnVibratorStateChangedListener.class);
-        OnVibratorStateChangedListener listener2 = mock(OnVibratorStateChangedListener.class);
+        OnVibratorStateChangedListener listener1 = newMockStateListener();
+        OnVibratorStateChangedListener listener2 = newMockStateListener();
         // Add listener1 on executor
         mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), listener1);
         // Add listener2 on main thread.
@@ -335,33 +411,54 @@
         verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
         verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
 
-        mVibrator.vibrate(1000);
+        mVibrator.vibrate(10);
+        assertStartsVibrating();
 
         verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(true);
         verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(true);
+        // The state changes back to false after vibration ends.
+        verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(2)).onVibratorStateChanged(false);
+        verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(2)).onVibratorStateChanged(false);
+    }
 
-        mVibrator.cancel();
-        assertStopsVibrating();
+    @LargeTest
+    @Test
+    public void testVibratorStateCallbackRemoval() {
+        if (!mVibrator.hasVibrator()) {
+            return;
+        }
+
+        OnVibratorStateChangedListener listener1 = newMockStateListener();
+        OnVibratorStateChangedListener listener2 = newMockStateListener();
+        // Add listener1 on executor
+        mVibrator.addVibratorStateListener(Executors.newSingleThreadExecutor(), listener1);
+        // Add listener2 on main thread.
+        mVibrator.addVibratorStateListener(listener2);
+        verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
+        verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(1)).onVibratorStateChanged(false);
 
         // Remove listener1 & listener2
         mVibrator.removeVibratorStateListener(listener1);
         mVibrator.removeVibratorStateListener(listener2);
-        reset(listener1);
-        reset(listener2);
 
         mVibrator.vibrate(1000);
         assertStartsVibrating();
 
-        verify(listener1, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
-                .onVibratorStateChanged(anyBoolean());
-        verify(listener2, timeout(CALLBACK_TIMEOUT_MILLIS).times(0))
-                .onVibratorStateChanged(anyBoolean());
+        // Wait the timeout to assert there was no more interactions with the removed listeners.
+        verify(listener1, after(CALLBACK_TIMEOUT_MILLIS).never()).onVibratorStateChanged(true);
+        // Previous call was blocking, so no need to wait for a timeout here as well.
+        verify(listener2, never()).onVibratorStateChanged(true);
+    }
+
+    private OnVibratorStateChangedListener newMockStateListener() {
+        OnVibratorStateChangedListener listener = mock(OnVibratorStateChangedListener.class);
+        mStateListenersCreated.add(listener);
+        return listener;
     }
 
     private void assertStartsThenStopsVibrating(long duration) {
         if (mVibrator.hasVibrator()) {
-            verify(mStateListener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
-                    .onVibratorStateChanged(true);
+            assertVibratorState(true);
             SystemClock.sleep(duration);
             assertVibratorState(false);
         }
@@ -379,7 +476,6 @@
         if (mVibrator.hasVibrator()) {
             verify(mStateListener, timeout(CALLBACK_TIMEOUT_MILLIS).atLeastOnce())
                     .onVibratorStateChanged(eq(expected));
-            reset(mStateListener);
         }
     }
 }
diff --git a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
index 7150d51..96309b8 100644
--- a/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
+++ b/tests/tests/os/src/android/os/storage/cts/StorageManagerTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
 import android.app.PendingIntent;
@@ -31,6 +33,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.ProxyFileDescriptorCallback;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.cts.R;
 import android.os.storage.OnObbStateChangeListener;
@@ -98,6 +101,11 @@
         }
     }
 
+    // b/215354735 - certain tests fail with fuse-bpf, so temporarily disable
+    private boolean isFuseBpf() throws Exception {
+        return SystemProperties.getBoolean("persist.sys.fuse.bpf.enable", false);
+    }
+
     private void doMountAndUnmountObbNormal(File outFile) throws IOException {
         final String canonPath = mountObb(R.raw.test1_new, outFile, OnObbStateChangeListener.MOUNTED);
 
@@ -221,6 +229,7 @@
         final boolean removable = volume.isRemovable();
         final boolean emulated = volume.isEmulated();
         if (emulated) {
+            assertFalse("Should not be externally managed", volume.isExternallyManaged());
             assertFalse("Should not be removable", removable);
             assertNull("Should not have fsUuid", fsUuid);
             assertEquals("Should have uuid_default", StorageManager.UUID_DEFAULT, uuid);
@@ -327,6 +336,7 @@
 
     @AppModeFull(reason = "Instant apps cannot access external storage")
     public void testCallback() throws Exception {
+        if (isFuseBpf()) return; // b/215354735
         final CountDownLatch mounted = new CountDownLatch(1);
         final CountDownLatch unmounted = new CountDownLatch(1);
         final StorageVolumeCallback callback = new StorageVolumeCallback() {
@@ -1010,6 +1020,32 @@
         }
     }
 
+    public void testComputeStorageCacheBytesHint() throws Exception {
+        File mockFile = mock(File.class);
+
+        when(mockFile.getUsableSpace()).thenReturn(10000L);
+        when(mockFile.getTotalSpace()).thenReturn(15000L);
+        final long resultHigh = mStorageManager.computeStorageCacheBytes(mockFile);
+        assertTrue("" + resultHigh + " expected to be greater than equal to 0", resultHigh >= 0L);
+        assertTrue("" + resultHigh + " expected to be less than equal to total space",
+                resultHigh <= mockFile.getTotalSpace());
+
+        when(mockFile.getUsableSpace()).thenReturn(10000L);
+        when(mockFile.getTotalSpace()).thenReturn(250000L);
+        final long resultLow = mStorageManager.computeStorageCacheBytes(mockFile);
+        assertTrue("" + resultLow + " expected to be greater than equal to 0", resultLow >= 0L);
+        assertTrue("" + resultLow + " expected to be less than equal to total space",
+                resultLow <= mockFile.getTotalSpace());
+
+        when(mockFile.getUsableSpace()).thenReturn(10000L);
+        when(mockFile.getTotalSpace()).thenReturn(100000L);
+        final long resultModerate = mStorageManager.computeStorageCacheBytes(mockFile);
+        assertTrue("" + resultModerate + " expected to be greater than equal to 0",
+                resultModerate >= 0L);
+        assertTrue("" + resultModerate + " expected to be less than equal to total space",
+                resultModerate <= mockFile.getTotalSpace());
+    }
+
     public static byte[] readFully(InputStream in) throws IOException {
         // Shamelessly borrowed from libcore.io.Streams
         try {
diff --git a/tests/tests/packageinstaller/TEST_MAPPING b/tests/tests/packageinstaller/TEST_MAPPING
index 0d326d5..a17298a 100644
--- a/tests/tests/packageinstaller/TEST_MAPPING
+++ b/tests/tests/packageinstaller/TEST_MAPPING
@@ -1,4 +1,30 @@
 {
+  "presubmit": [
+    {
+      "name": "CtsAdminPackageInstallerTestCases"
+    },
+    {
+      "name": "CtsPackageInstallTestCases"
+    },
+    {
+      "name": "CtsPackageInstallAppOpDefaultTestCases"
+    },
+    {
+      "name": "CtsPackageInstallAppOpDeniedTestCases"
+    },
+    {
+      "name": "CtsNoPermissionTestCases"
+    },
+    {
+      "name": "CtsNoPermissionTestCases25"
+    },
+    {
+      "name": "CtsPackageInstallerTapjackingTestCases"
+    },
+    {
+      "name": "CtsPackageUninstallTestCases"
+    }
+  ],
   "imports": [
     {
       "path": "cts/tests/tests/packageinstaller/atomicinstall"
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
index 6fc29db..5b93092 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/PackageInstallerTestBase.kt
@@ -50,7 +50,6 @@
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.concurrent.TimeUnit
 
-const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
 const val TEST_APK_PACKAGE_NAME = "android.packageinstaller.emptytestapp.cts"
 const val TEST_APK_EXTERNAL_LOCATION = "/data/local/tmp/cts/packageinstaller"
 const val INSTALL_ACTION_CB = "PackageInstallerTestBase.install_cb"
@@ -66,6 +65,10 @@
 const val INSTALL_INSTANT_APP = 0x00000800
 
 open class PackageInstallerTestBase {
+    companion object {
+        const val TEST_APK_NAME = "CtsEmptyTestApp.apk"
+    }
+
     @get:Rule
     val installDialogStarter = ActivityTestRule(FutureResultActivity::class.java)
 
@@ -138,6 +141,13 @@
     }
 
     protected fun startInstallationViaSession(installFlags: Int): CompletableFuture<Int> {
+        return startInstallationViaSession(installFlags, TEST_APK_NAME)
+    }
+
+    private fun createSession(
+        installFlags: Int,
+        isMultiPackage: Boolean
+    ): Pair<Int, PackageInstaller.Session> {
         val pi = pm.packageInstaller
 
         // Create session
@@ -146,20 +156,31 @@
         if (installFlags and INSTALL_INSTANT_APP != 0) {
             sessionParam.setInstallAsInstantApp(true)
         }
+        if (isMultiPackage) {
+            sessionParam.setMultiPackage()
+        }
 
         val sessionId = pi.createSession(sessionParam)
         val session = pi.openSession(sessionId)!!
 
+        return Pair(sessionId, session)
+    }
+
+    private fun writeSession(session: PackageInstaller.Session, apkName: String) {
+        val apkFile = File(context.filesDir, apkName)
         // Write data to session
         apkFile.inputStream().use { fileOnDisk ->
-            session.openWrite(TEST_APK_NAME, 0, -1).use { sessionFile ->
+            session.openWrite(apkName, 0, -1).use { sessionFile ->
                 fileOnDisk.copyTo(sessionFile)
             }
         }
+    }
 
+    private fun commitSession(session: PackageInstaller.Session): CompletableFuture<Int> {
         // Commit session
         val dialog = FutureResultActivity.doAndAwaitStart {
-            val pendingIntent = PendingIntent.getBroadcast(context, 0, Intent(INSTALL_ACTION_CB),
+            val pendingIntent = PendingIntent.getBroadcast(
+                    context, 0, Intent(INSTALL_ACTION_CB),
                     FLAG_UPDATE_CURRENT or FLAG_MUTABLE)
             session.commit(pendingIntent.intentSender)
         }
@@ -170,6 +191,28 @@
         return dialog
     }
 
+    protected fun startInstallationViaSession(
+        installFlags: Int,
+        apkName: String
+    ): CompletableFuture<Int> {
+        val (sessionId, session) = createSession(installFlags, false)
+        writeSession(session, apkName)
+        return commitSession(session)
+    }
+
+    protected fun startInstallationViaMultiPackageSession(
+        installFlags: Int,
+        vararg apkNames: String
+    ): CompletableFuture<Int> {
+        val (sessionId, session) = createSession(installFlags, true)
+        for (apkName in apkNames) {
+            val (childSessionId, childSession) = createSession(installFlags, false)
+            writeSession(childSession, apkName)
+            session.addChildSessionId(childSessionId)
+        }
+        return commitSession(session)
+    }
+
     /**
      * Start an installation via a session
      */
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
index e81f1d1..957b66f 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/SessionTest.kt
@@ -63,6 +63,27 @@
     }
 
     /**
+     * Check that we can install an app via a package-installer session
+     */
+    @Test
+    fun confirmMultiPackageInstallation() {
+        val installation = startInstallationViaMultiPackageSession(
+                installFlags = 0,
+                PackageInstallerTestBase.TEST_APK_NAME
+        )
+        clickInstallerUIButton(INSTALL_BUTTON_ID)
+
+        // Install should have succeeded
+        assertEquals(STATUS_SUCCESS, getInstallSessionResult())
+        assertInstalled()
+
+        // Even when the install succeeds the install confirm dialog returns 'canceled'
+        assertEquals(RESULT_CANCELED, installation.get(TIMEOUT, TimeUnit.MILLISECONDS))
+
+        assertTrue(AppOpsUtils.allowedOperationLogged(context.packageName, APP_OP_STR))
+    }
+
+    /**
      * Check that we can set an app category for an app we installed
      */
     @Test
diff --git a/tests/tests/packageinstaller/nopermission/AndroidTest.xml b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
index 08ae8cc..49fcf89 100644
--- a/tests/tests/packageinstaller/nopermission/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
index e60e53a..43a3844 100644
--- a/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
+++ b/tests/tests/packageinstaller/nopermission/src/android.packageinstaller.nopermission.cts/NoPermissionTests.kt
@@ -113,7 +113,8 @@
 
     @Before
     fun registerInstallResultReceiver() {
-        context.registerReceiver(receiver, IntentFilter(ACTION))
+        context.registerReceiver(receiver, IntentFilter(ACTION),
+                Context.RECEIVER_EXPORTED_UNAUDITED)
     }
 
     @Before
diff --git a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
index 3c69674..c84a81f 100644
--- a/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
+++ b/tests/tests/packageinstaller/nopermission25/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="mkdir -p /data/local/tmp/cts/nopermission" />
diff --git a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
index 6aaeade..e501c90 100644
--- a/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
+++ b/tests/tests/packageinstaller/test-apps/SelfUninstallingTestApp/Android.bp
@@ -30,7 +30,6 @@
 
     // tag this module as a cts test artifact
     test_suites: [
-        "arcts",
         "cts",
         "general-tests",
         "sts",
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index 8e4ff03..424da2e 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -20,6 +20,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk30ModuleController" />
 
@@ -77,6 +78,7 @@
         <option name="push" value="CtsInstallPermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionUserApp.apk" />
         <option name="push" value="CtsInstallPermissionEscalatorApp.apk->/data/local/tmp/cts/permissions/CtsInstallPermissionEscalatorApp.apk" />
         <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
+        <option name="push" value="CtsAppToTestSelfRevokePermission.apk->/data/local/tmp/cts/permissions/CtsAppToTestSelfRevokePermission.apk" />
         <option name="push" value="AppThatDefinesUndefinedPermissionGroupElement.apk->/data/local/tmp/cts/permissions/AppThatDefinesUndefinedPermissionGroupElement.apk" />
         <option name="push" value="CtsAppThatDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatDefinesPermissionA.apk" />
         <option name="push" value="CtsAppThatAlsoDefinesPermissionA.apk->/data/local/tmp/cts/permissions/CtsAppThatAlsoDefinesPermissionA.apk" />
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp b/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp
new file mode 100644
index 0000000..88d1c60
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/Android.bp
@@ -0,0 +1,35 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAppToTestSelfRevokePermission",
+    defaults: [
+        "cts_defaults",
+        "mts-target-sdk-version-current",
+    ],
+    min_sdk_version: "30",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "mts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/AndroidManifest.xml b/tests/tests/permission/AppToTestSelfRevokePermission/AndroidManifest.xml
new file mode 100644
index 0000000..2e2a759
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/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="android.permission.cts.apptotestselfrevokepermission">
+
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
+
+    <application>
+        <activity android:name=".RevokePermission" android:exported="true"
+                  android:visibleToInstantApps="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java b/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
new file mode 100644
index 0000000..bc40924
--- /dev/null
+++ b/tests/tests/permission/AppToTestSelfRevokePermission/src/android/permission/cts/apptotestselfrevokepermission/RevokePermission.java
@@ -0,0 +1,37 @@
+/*
+ * 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.permission.cts.apptotestselfrevokepermission;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.Arrays;
+
+public class RevokePermission extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = getIntent();
+        String[] permissions = intent.getStringArrayExtra("permissions");
+        if (permissions.length == 1) {
+            getApplicationContext().selfRevokePermission(permissions[0]);
+        } else {
+            getApplicationContext().selfRevokePermissions(Arrays.asList(permissions));
+        }
+    }
+}
diff --git a/tests/tests/permission/nativeTests/AndroidTest.xml b/tests/tests/permission/nativeTests/AndroidTest.xml
index 23064c5..7d1bdb0 100644
--- a/tests/tests/permission/nativeTests/AndroidTest.xml
+++ b/tests/tests/permission/nativeTests/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
     <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
diff --git a/tests/tests/permission/sdk28/AndroidTest.xml b/tests/tests/permission/sdk28/AndroidTest.xml
index e47b1ac..d1b5ac6 100644
--- a/tests/tests/permission/sdk28/AndroidTest.xml
+++ b/tests/tests/permission/sdk28/AndroidTest.xml
@@ -20,6 +20,7 @@
     <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" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsPermissionTestCasesSdk28.apk" />
diff --git a/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java b/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
index c3631bc..bba9963 100644
--- a/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppIdleStatePermissionTest.java
@@ -63,4 +63,30 @@
                     + pkgNames);
         }
     }
+
+    /**
+     * Verify that the {@link android.Manifest.permission#CHANGE_APP_LAUNCH_TIME_ESTIMATE}
+     * permission is only held by at most one package.
+     */
+    @Test
+    public void testChangeAppLaunchEstimatePermission() throws Exception {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        final List<PackageInfo> holding = pm.getPackagesHoldingPermissions(new String[]{
+                android.Manifest.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE
+        }, PackageManager.MATCH_SYSTEM_ONLY);
+
+        int count = 0;
+        String pkgNames = "";
+        for (PackageInfo pkg : holding) {
+            int uid = pm.getApplicationInfo(pkg.packageName, 0).uid;
+            if (UserHandle.isApp(uid)) {
+                pkgNames += pkg.packageName + "\n";
+                count++;
+            }
+        }
+        if (count > 1) {
+            fail("Only one app may hold the CHANGE_APP_LAUNCH_TIME_ESTIMATE permission;"
+                    + " found packages: \n" + pkgNames);
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java b/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java
new file mode 100644
index 0000000..ce1d554
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/SelfRevokeRuntimePermissionTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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.permission.cts;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.Manifest.permission.CAMERA;
+import static android.Manifest.permission.HIGH_SAMPLING_RATE_SENSORS;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
+import static android.content.pm.PackageManager.GET_PERMISSIONS;
+import static android.permission.cts.PermissionUtils.getPermissionFlags;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.setPermissionFlags;
+
+import static com.android.compatibility.common.util.SystemUtil.eventually;
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.ActivityManager;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.provider.DeviceConfig;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SelfRevokeRuntimePermissionTest {
+    private static final String APP_PKG_NAME =
+            "android.permission.cts.apptotestselfrevokepermission";
+    private static final String APK =
+            "/data/local/tmp/cts/permissions/CtsAppToTestSelfRevokePermission.apk";
+    private static final long ONE_TIME_TIMEOUT_MILLIS = 500;
+    private static final long KILLED_DELAY_MILLIS = 100;
+    private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 1000;
+
+    private final Instrumentation mInstrumentation =
+            InstrumentationRegistry.getInstrumentation();
+    private final Context mContext = mInstrumentation.getTargetContext();
+    private final ActivityManager mActivityManager =
+            mContext.getSystemService(ActivityManager.class);
+    private final UiDevice mUiDevice = UiDevice.getInstance(mInstrumentation);
+    private String mOldOneTimePermissionTimeoutValue;
+
+    @Before
+    public void wakeUpScreen() {
+        SystemUtil.runShellCommand("input keyevent KEYCODE_WAKEUP");
+        SystemUtil.runShellCommand("input keyevent 82");
+    }
+
+    @Before
+    public void prepareDeviceForOneTime() {
+        runWithShellPermissionIdentity(() -> {
+            mOldOneTimePermissionTimeoutValue = DeviceConfig.getProperty("permissions",
+                    "one_time_permissions_timeout_millis");
+            DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+                    Long.toString(ONE_TIME_TIMEOUT_MILLIS), false);
+            DeviceConfig.setProperty("permissions", "one_time_permissions_killed_delay_millis",
+                    Long.toString(KILLED_DELAY_MILLIS), false);
+        });
+    }
+
+    @After
+    public void uninstallApp() {
+        runShellCommand("pm uninstall " + APP_PKG_NAME);
+    }
+
+    @After
+    public void restoreDeviceForOneTime() {
+        runWithShellPermissionIdentity(
+                () -> DeviceConfig.setProperty("permissions", "one_time_permissions_timeout_millis",
+                        mOldOneTimePermissionTimeoutValue, false));
+    }
+
+    @Test
+    public void testMultiplePermissions() throws Throwable {
+        // Trying to revoke multiple permissions including some from the same permission group
+        // should work.
+        installApp();
+        String[] permissions = new String[] {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION, CAMERA};
+        for (String permission : permissions) {
+            grantPermission(APP_PKG_NAME, permission);
+        }
+        revokePermissions(permissions);
+        placeAppInBackground();
+        for (String permission : permissions) {
+            assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                    permission);
+        }
+        uninstallApp();
+    }
+
+    @Test
+    public void testNormalPermission() throws Throwable {
+        // Trying to revoke a normal (non-runtime) permission should not actually revoke it.
+        installApp();
+        revokePermission(HIGH_SAMPLING_RATE_SENSORS);
+        placeAppInBackground();
+        try {
+            waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                    HIGH_SAMPLING_RATE_SENSORS);
+            fail("android.permission.HIGH_SAMPLING_RATE_SENSORS was revoked");
+        } catch (Throwable expected) {
+            assertEquals(HIGH_SAMPLING_RATE_SENSORS + " not revoked",
+                    expected.getMessage());
+        }
+        uninstallApp();
+    }
+
+    @Test
+    public void testKillTriggersRevocation() throws Throwable {
+        // Killing the process should start the revocation right away
+        installApp();
+        grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+        revokePermission(ACCESS_FINE_LOCATION);
+        killApp();
+        assertDenied(KILLED_DELAY_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                ACCESS_FINE_LOCATION);
+        uninstallApp();
+    }
+
+    @Test
+    public void testNoRevocationWhileForeground() throws Throwable {
+        // Even after calling selfRevokePermission, the permission should stay granted while the
+        // package is in the foreground.
+        installApp();
+        grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+        revokePermission(ACCESS_FINE_LOCATION);
+        try {
+            waitUntilPermissionRevoked(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                    ACCESS_FINE_LOCATION);
+            fail("android.permission.ACCESS_FINE_LOCATION was revoked");
+        } catch (Throwable expected) {
+            assertEquals(ACCESS_FINE_LOCATION + " not revoked",
+                    expected.getMessage());
+        }
+        uninstallApp();
+    }
+
+    @Test
+    public void testWhileInUseLocationPermission() throws Throwable {
+        // After revoking any location permission and leaving the app in background for a while, the
+        // location permission group should be revoked.
+        installApp();
+        grantPermission(APP_PKG_NAME, ACCESS_COARSE_LOCATION);
+        grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+        revokePermission(ACCESS_FINE_LOCATION);
+        placeAppInBackground();
+        assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                ACCESS_FINE_LOCATION);
+        assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                ACCESS_COARSE_LOCATION);
+        uninstallApp();
+    }
+
+    @Test
+    public void testNoRepromptWhenUserFixed() throws Throwable {
+        // If a permission has been USER_FIXED to not granted, then revoking the permission group
+        // should leave the USER_FIXED flag.
+        installApp();
+        grantPermission(APP_PKG_NAME, ACCESS_FINE_LOCATION);
+        setPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_FIXED,
+                FLAG_PERMISSION_USER_FIXED);
+        revokePermission(ACCESS_FINE_LOCATION);
+        placeAppInBackground();
+        assertDenied(ONE_TIME_TIMEOUT_MILLIS + ONE_TIME_TIMER_UPPER_GRACE_PERIOD,
+                ACCESS_FINE_LOCATION);
+        int flags = getPermissionFlags(APP_PKG_NAME, ACCESS_BACKGROUND_LOCATION);
+        assertEquals(FLAG_PERMISSION_USER_FIXED, flags & FLAG_PERMISSION_USER_FIXED);
+        uninstallApp();
+    }
+
+
+    private void installApp() {
+        runShellCommand("pm install -r " + APK);
+    }
+
+    private void placeAppInBackground() {
+        boolean[] hasExited = {false};
+        try {
+            new Thread(() -> {
+                while (!hasExited[0]) {
+                    mUiDevice.pressHome();
+                    mUiDevice.pressBack();
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                    }
+                }
+            }).start();
+            eventually(() -> {
+                runWithShellPermissionIdentity(() -> {
+                    if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+                            <= IMPORTANCE_FOREGROUND) {
+                        throw new AssertionError("Unable to exit application");
+                    }
+                });
+            });
+        } finally {
+            hasExited[0] = true;
+        }
+    }
+
+    /**
+     * Start the app. The app will revoke the permission.
+     */
+    private void revokePermission(String permName) {
+        revokePermissions(new String[] { permName });
+    }
+
+    private void revokePermissions(String[] permissions) {
+        runShellCommand("am start-activity -W -n " + APP_PKG_NAME  + "/.RevokePermission"
+                + " --esa permissions " + String.join(",", permissions));
+    }
+
+    private void killApp() {
+        runShellCommand("am force-stop " + APP_PKG_NAME);
+    }
+
+    private void assertGrantedState(String s, String permissionName, int permissionGranted,
+            long timeoutMillis) {
+        eventually(() -> Assert.assertEquals(s, permissionGranted,
+                mContext.getPackageManager().checkPermission(permissionName, APP_PKG_NAME)),
+                timeoutMillis);
+    }
+
+    private void assertGranted(long timeoutMillis, String permissionName) {
+        assertGrantedState("Permission was never granted", permissionName,
+                PackageManager.PERMISSION_GRANTED, timeoutMillis);
+    }
+
+    private void assertDenied(long timeoutMillis, String permissionName) {
+        assertGrantedState("Permission was never revoked", permissionName,
+                PackageManager.PERMISSION_DENIED, timeoutMillis);
+    }
+
+    private void waitUntilPermissionRevoked(long timeoutMillis, String permName) throws Throwable {
+        try {
+            eventually(() -> {
+                PackageInfo appInfo = mContext.getPackageManager().getPackageInfo(APP_PKG_NAME,
+                        GET_PERMISSIONS);
+
+                for (int i = 0; i < appInfo.requestedPermissions.length; i++) {
+                    if (appInfo.requestedPermissions[i].equals(permName)
+                            && (
+                            (appInfo.requestedPermissionsFlags[i] & REQUESTED_PERMISSION_GRANTED)
+                                    == 0)) {
+                        return;
+                    }
+                }
+
+                fail(permName + " not revoked");
+            }, timeoutMillis);
+        } catch (RuntimeException e) {
+            throw e.getCause();
+        }
+    }
+}
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
index 0016704..63cd399 100755
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionsSystemTest.java
@@ -24,6 +24,8 @@
 import static android.Manifest.permission.BLUETOOTH_ADMIN;
 import static android.Manifest.permission.BLUETOOTH_CONNECT;
 import static android.Manifest.permission.BLUETOOTH_SCAN;
+import static android.Manifest.permission.BODY_SENSORS;
+import static android.Manifest.permission.BODY_SENSORS_BACKGROUND;
 import static android.Manifest.permission.READ_CALL_LOG;
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
@@ -125,10 +127,13 @@
                     // STOPSHIP(b/184180558): replace with "S" once SDK is finalized
                     assertSplit(split, Build.VERSION_CODES.R + 1, BLUETOOTH, BLUETOOTH_ADMIN);
                     break;
+                case BODY_SENSORS:
+                    // STOPSHIP(b/212583342): replace with "T" once SDK is finalized
+                    assertSplit(split, Build.VERSION_CODES.S_V2 + 1, BODY_SENSORS_BACKGROUND);
             }
         }
 
-        assertEquals(13, seenSplits.size());
+        assertEquals(14, seenSplits.size());
     }
 
     private void assertSplit(SplitPermissionInfo split, int targetSdk, String... permission) {
diff --git a/tests/tests/permission/telephony/AndroidTest.xml b/tests/tests/permission/telephony/AndroidTest.xml
index cdf5ff1..3a8dc5e 100644
--- a/tests/tests/permission/telephony/AndroidTest.xml
+++ b/tests/tests/permission/telephony/AndroidTest.xml
@@ -21,6 +21,7 @@
     <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" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
 
     <!-- Install main test suite apk -->
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/tests/permission2/AndroidTest.xml b/tests/tests/permission2/AndroidTest.xml
index cc1bc7f..54ea94b 100644
--- a/tests/tests/permission2/AndroidTest.xml
+++ b/tests/tests/permission2/AndroidTest.xml
@@ -20,6 +20,7 @@
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user"/>
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index b4dbf59..a1ab5cc 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -55,6 +55,7 @@
     <protected-broadcast android:name="android.intent.action.PACKAGE_VERIFIED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENDED" />
     <protected-broadcast android:name="android.intent.action.PACKAGES_UNSUSPENDED" />
+    <protected-broadcast android:name="android.intent.action.PACKAGES_SUSPENSION_CHANGED" />
     <protected-broadcast android:name="android.intent.action.PACKAGE_UNSUSPENDED_MANUALLY" />
     <protected-broadcast android:name="android.intent.action.DISTRACTING_PACKAGES_CHANGED" />
     <protected-broadcast android:name="android.intent.action.ACTION_PREFERRED_ACTIVITY_CHANGED" />
@@ -63,6 +64,7 @@
     <protected-broadcast android:name="android.intent.action.CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.intent.action.SPLIT_CONFIGURATION_CHANGED" />
     <protected-broadcast android:name="android.intent.action.LOCALE_CHANGED" />
+    <protected-broadcast android:name="android.intent.action.APPLICATION_LOCALE_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BATTERY_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BATTERY_LEVEL_CHANGED" />
     <protected-broadcast android:name="android.intent.action.BATTERY_LOW" />
@@ -201,6 +203,11 @@
         android:name="android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_DEVICE_AVAILABLE" />
+    <protected-broadcast android:name="android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE" />
+    <protected-broadcast
+        android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" />
     <protected-broadcast
@@ -247,6 +254,11 @@
         android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_DELIVERY" />
     <protected-broadcast
         android:name="android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONNECTION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_CONF_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED" />
+    <protected-broadcast android:name="android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED" />
     <protected-broadcast
         android:name="android.bluetooth.action.TETHERING_STATE_CHANGED" />
     <protected-broadcast android:name="android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED" />
@@ -550,6 +562,8 @@
     <protected-broadcast android:name="com.android.server.telecom.intent.action.CALLS_ADD_ENTRY" />
     <protected-broadcast android:name="com.android.settings.location.MODE_CHANGING" />
     <protected-broadcast android:name="com.android.settings.bluetooth.ACTION_DISMISS_PAIRING" />
+    <protected-broadcast android:name="com.android.settings.network.DELETE_SUBSCRIPTION" />
+    <protected-broadcast android:name="com.android.settings.network.SWITCH_TO_SUBSCRIPTION" />
     <protected-broadcast android:name="com.android.settings.wifi.action.NETWORK_REQUEST" />
 
     <protected-broadcast android:name="NotificationManagerService.TIMEOUT" />
@@ -616,7 +630,11 @@
 
     <protected-broadcast android:name="com.android.server.fingerprint.ACTION_LOCKOUT_RESET" />
     <protected-broadcast android:name="android.net.wifi.PASSPOINT_ICON_RECEIVED" />
+
     <protected-broadcast android:name="com.android.server.notification.CountdownConditionProvider" />
+    <protected-broadcast android:name="android.server.notification.action.ENABLE_NAS" />
+    <protected-broadcast android:name="android.server.notification.action.DISABLE_NAS" />
+    <protected-broadcast android:name="android.server.notification.action.LEARNMORE_NAS" />
 
     <protected-broadcast android:name="com.android.internal.location.ALARM_WAKEUP" />
     <protected-broadcast android:name="com.android.internal.location.ALARM_TIMEOUT" />
@@ -693,8 +711,8 @@
     <!-- Added in S -->
     <protected-broadcast android:name="android.scheduling.action.REBOOT_READY" />
     <protected-broadcast android:name="android.app.action.DEVICE_POLICY_CONSTANTS_CHANGED" />
-
     <protected-broadcast android:name="android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.SHOW_NEW_USER_DISCLAIMER" />
 
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
@@ -738,7 +756,16 @@
         android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_writeContacts"
         android:description="@string/permdesc_writeContacts"
-        android:protectionLevel="dangerous" />
+      android:protectionLevel="dangerous" />
+
+    <!-- Allows an application to set default account for new contacts.
+        <p> This permission is only granted to system applications fulfilling the Contacts app role.
+        <p>Protection level: internal|role
+        @SystemApi
+        @hide
+    -->
+    <permission android:name="android.permission.SET_DEFAULT_ACCOUNT_FOR_CONTACTS"
+        android:protectionLevel="internal|role" />
 
     <!-- ====================================================================== -->
     <!-- Permissions for accessing user's calendar                              -->
@@ -1451,10 +1478,28 @@
          measure what is happening inside their body, such as heart rate.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.BODY_SENSORS"
-        android:permissionGroup="android.permission-group.UNDEFINED"
-        android:label="@string/permlab_bodySensors"
-        android:description="@string/permdesc_bodySensors"
-        android:protectionLevel="dangerous" />
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_bodySensors"
+                android:description="@string/permdesc_bodySensors"
+                android:backgroundPermission="android.permission.BODY_SENSORS_BACKGROUND"
+                android:protectionLevel="dangerous" />
+
+    <!-- Allows an application to access data from sensors that the user uses to measure what is
+         happening inside their body, such as heart rate. If you're requesting this permission, you
+         must also request {@link #BODY_SENSORS}. Requesting this permission by itself doesn't give
+         you Body sensors access.
+         <p>Protection level: dangerous
+
+         <p> This is a hard restricted permission which cannot be held by an app until
+         the installer on record allowlists the permission. For more details see
+         {@link android.content.pm.PackageInstaller.SessionParams#setWhitelistedRestrictedPermissions(Set)}.
+    -->
+    <permission android:name="android.permission.BODY_SENSORS_BACKGROUND"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_bodySensors_background"
+                android:description="@string/permdesc_bodySensors_background"
+                android:protectionLevel="dangerous"
+                android:permissionFlags="hardRestricted" />
 
     <!-- Allows an app to use fingerprint hardware.
          <p>Protection level: normal
@@ -1476,6 +1521,28 @@
         android:description="@string/permdesc_useBiometric"
         android:protectionLevel="normal" />
 
+   <!-- ======================================================================= -->
+    <!-- Permissions for posting notifications                                  -->
+    <!-- ====================================================================== -->
+    <eat-comment />
+
+    <!-- Used for permissions that are associated with posting notifications
+    -->
+    <permission-group android:name="android.permission-group.NOTIFICATIONS"
+          android:icon="@drawable/ic_notifications_alerted"
+          android:label="@string/permgrouplab_notifications"
+          android:description="@string/permgroupdesc_notifications"
+          android:priority="850" />
+
+    <!-- Allows an app to post notifications
+         <p>Protection level: dangerous
+    -->
+    <permission android:name="android.permission.POST_NOTIFICATIONS"
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:label="@string/permlab_postNotification"
+                android:description="@string/permdesc_postNotification"
+                android:protectionLevel="dangerous" />
+
     <!-- ====================================================================== -->
     <!-- REMOVED PERMISSIONS                                                    -->
     <!-- ====================================================================== -->
@@ -1702,6 +1769,13 @@
     <permission android:name="android.permission.ACCESS_MOCK_LOCATION"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows automotive applications to control location
+         suspend state for power management use cases.
+         <p>Not for use by third-party applications.
+    -->
+    <permission android:name="android.permission.AUTOMOTIVE_GNSS_CONTROLS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- ======================================= -->
     <!-- Permissions for accessing networks -->
     <!-- ======================================= -->
@@ -1739,6 +1813,13 @@
         android:label="@string/permlab_changeWifiState"
         android:protectionLevel="normal" />
 
+    <!-- @SystemApi @hide Allows applications to enable/disable wifi auto join. This permission
+         is used to let OEMs grant their trusted app access to a subset of privileged wifi APIs
+         to improve wifi performance.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MANAGE_WIFI_AUTO_JOIN"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi @hide Allows apps to create and manage IPsec tunnels.
          <p>Only granted to applications that are currently bound by the
          system for creating and managing IPsec-based interfaces.
@@ -1851,7 +1932,7 @@
          @hide This should only be used by ManagedProvisioning app.
     -->
     <permission android:name="android.permission.NETWORK_MANAGED_PROVISIONING"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows Carrier Provisioning to call methods in Networking services
          <p>Not for use by any other third-party or privileged applications.
@@ -1968,10 +2049,10 @@
     <!-- Required to be able to advertise and connect to nearby devices via Wi-Fi.
          <p>Protection level: dangerous -->
     <permission android:name="android.permission.NEARBY_WIFI_DEVICES"
-        android:permissionGroup="android.permission-group.UNDEFINED"
-        android:description="@string/permdesc_nearby_wifi_devices"
-        android:label="@string/permlab_nearby_wifi_devices"
-        android:protectionLevel="dangerous" />
+                android:permissionGroup="android.permission-group.UNDEFINED"
+                android:description="@string/permdesc_nearby_wifi_devices"
+                android:label="@string/permlab_nearby_wifi_devices"
+                android:protectionLevel="dangerous" />
 
     <!-- @SystemApi @TestApi Allows an application to suspend other apps, which will prevent the
          user from using them until they are unsuspended.
@@ -2307,10 +2388,10 @@
     <permission android:name="android.permission.OEM_UNLOCK_STATE"
         android:protectionLevel="signature" />
 
-    <!-- @hide Allows querying state of PersistentDataBlock
+    <!-- @SystemApi @hide Allows querying state of PersistentDataBlock
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.ACCESS_PDB_STATE"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows testing if a passwords is forbidden by the admins.
          @hide <p>Not for use by third-party applications. -->
@@ -2368,7 +2449,7 @@
     <!-- @SystemApi @TestApi Allows read access to privileged phone state.
          @hide Used internally. -->
     <permission android:name="android.permission.READ_PRIVILEGED_PHONE_STATE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- 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
@@ -2383,7 +2464,7 @@
     <permission android:name="android.permission.READ_ACTIVE_EMERGENCY_SESSION"
         android:protectionLevel="signature" />
 
-    <!-- Allows listen permission to always reported signal strength.
+    <!-- Allows listen permission to always reported system signal strength.
          @hide Used internally. -->
     <permission android:name="android.permission.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH"
         android:protectionLevel="signature" />
@@ -2593,7 +2674,7 @@
          third-party apps.
     -->
     <permission android:name="android.permission.MANAGE_DOCUMENTS"
-        android:protectionLevel="signature|documenter" />
+        android:protectionLevel="signature|role" />
 
     <!-- Allows an application to manage access to crates, usually as part
          of a crates picker.
@@ -2610,7 +2691,7 @@
          <p>Not for use by third-party applications.
     -->
     <permission android:name="android.permission.CACHE_CONTENT"
-        android:protectionLevel="signature|documenter" />
+        android:protectionLevel="signature|role" />
 
     <!-- @SystemApi @hide
          Allows an application to aggressively allocate disk space.
@@ -2684,25 +2765,25 @@
          user-targeted broadcasts.  This permission is not available to
          third party applications. -->
     <permission android:name="android.permission.INTERACT_ACROSS_USERS"
-        android:protectionLevel="signature|privileged|development" />
+        android:protectionLevel="signature|privileged|development|role" />
 
     <!-- @SystemApi Fuller form of {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
          that removes restrictions on where broadcasts can be sent and allows other
          types of interactions
          @hide -->
     <permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
-        android:protectionLevel="signature|installer" />
+        android:protectionLevel="signature|installer|role" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
 
     <!-- Allows interaction across profiles in the same profile group. -->
     <permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
         android:protectionLevel="signature|appop" />
 
-    <!-- Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that they can
-         interact across profiles in the same profile group.
+    <!-- @SystemApi Allows configuring apps to have the INTERACT_ACROSS_PROFILES permission so that
+         they can interact across profiles in the same profile group.
          @hide -->
     <permission android:name="android.permission.CONFIGURE_INTERACT_ACROSS_PROFILES"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- @SystemApi @hide Allows starting activities across profiles in the same profile group. -->
     <permission android:name="android.permission.START_CROSS_PROFILE_ACTIVITIES"
@@ -2723,17 +2804,27 @@
     <permission android:name="android.permission.CREATE_USERS"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi @hide Allows an application to call APIs that allow it to query users on the
+         device. -->
+    <permission android:name="android.permission.QUERY_USERS"
+                android:protectionLevel="signature|role" />
+
     <!-- Allows an application to access data blobs across users. -->
     <permission android:name="android.permission.ACCESS_BLOBS_ACROSS_USERS"
         android:protectionLevel="signature|privileged|development|role" />
 
-    <!-- @hide Allows an application to set the profile owners and the device owner.
+    <!-- @SystemApi @hide Allows an application to set the profile owners and the device owner.
          This permission is not available to third party applications.-->
     <permission android:name="android.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS"
-        android:protectionLevel="signature"
+        android:protectionLevel="signature|role|setup"
         android:label="@string/permlab_manageProfileAndDeviceOwners"
         android:description="@string/permdesc_manageProfileAndDeviceOwners" />
 
+    <!-- @SystemApi @hide Allows an application to query device policies set by any admin on
+         the device.-->
+    <permission android:name="android.permission.QUERY_ADMIN_POLICY"
+                android:protectionLevel="signature|role" />
+
     <!-- @TestApi @hide Allows an application to reset the record of previous system update freeze
          periods. -->
     <permission android:name="android.permission.CLEAR_FREEZE_PERIOD"
@@ -2760,7 +2851,7 @@
 
     <!-- @SystemApi @TestApi @hide Allows an application to change to remove/kill tasks -->
     <permission android:name="android.permission.REMOVE_TASKS"
-        android:protectionLevel="signature|documenter|recents" />
+        android:protectionLevel="signature|recents|role" />
 
     <!-- @deprecated Use MANAGE_ACTIVITY_TASKS instead.
          @SystemApi @TestApi @hide Allows an application to create/manage/remove stacks -->
@@ -2783,7 +2874,7 @@
 
     <!-- @SystemApi @hide Allows an application to start activities from background -->
     <permission android:name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"
-        android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier" />
+        android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role" />
 
     <!-- Allows an application to start foreground services from the background at any time.
          <em>This permission is not for use by third-party applications</em>,
@@ -2897,7 +2988,13 @@
     <permission android:name="android.permission.SET_DISPLAY_OFFSET"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows a companion app to run in the background.
+    <!-- Allows a companion app to run in the background. This permission implies
+         {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+         and allows to start a foreground service from the background.
+         If an app does not have to run in the background, but only needs to start a foreground
+         service from the background, consider using
+         {@link android.Manifest.permission#REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND},
+         which is less powerful.
          <p>Protection level: normal
     -->
     <permission android:name="android.permission.REQUEST_COMPANION_RUN_IN_BACKGROUND"
@@ -2907,6 +3004,7 @@
 
     <!-- Allows a companion app to start a foreground service from the background.
          {@see android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND}
+         <p>Protection level: normal
          -->
     <permission android:name="android.permission.REQUEST_COMPANION_START_FOREGROUND_SERVICES_FROM_BACKGROUND"
         android:protectionLevel="normal"/>
@@ -2927,6 +3025,35 @@
     <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_WATCH"
                 android:protectionLevel="normal" />
 
+    <!-- Allows application to request to be associated with a virtual display capable of streaming
+         Android applications
+         ({@link android.companion.AssociationRequest#DEVICE_PROFILE_APP_STREAMING})
+         by {@link android.companion.CompanionDeviceManager}.
+        <p>Not for use by third-party applications.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_APP_STREAMING"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- Allows application to request to be associated with a vehicle head unit capable of
+         automotive projection
+         ({@link android.companion.AssociationRequest#DEVICE_PROFILE_AUTOMOTIVE_PROJECTION})
+         by {@link android.companion.CompanionDeviceManager}.
+        <p>Not for use by third-party applications.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.REQUEST_COMPANION_PROFILE_AUTOMOTIVE_PROJECTION"
+                android:protectionLevel="internal|role" />
+
+    <!-- Allows an application to create a "self-managed" association.
+         @hide
+         @SystemApi
+    -->
+    <permission android:name="android.permission.REQUEST_COMPANION_SELF_MANAGED"
+                android:protectionLevel="signature|privileged" />
+
     <!-- Allows a companion app to associate to Wi-Fi.
          <p>Only for use by a single pre-approved app.
          @hide
@@ -2943,13 +3070,11 @@
                 android:protectionLevel="signature" />
 
     <!-- Allows an app to set and release automotive projection.
-         <p>Once permissions can be granted via role-only, this needs to be changed to
-          protectionLevel="role" and added to the SYSTEM_AUTOMOTIVE_PROJECTION role.
          @hide
          @SystemApi
     -->
     <permission android:name="android.permission.TOGGLE_AUTOMOTIVE_PROJECTION"
-                android:protectionLevel="signature|privileged" />
+                android:protectionLevel="internal|role" />
 
     <!-- Allows an app to prevent non-system-overlay windows from being drawn on top of it -->
     <permission android:name="android.permission.HIDE_OVERLAY_WINDOWS"
@@ -2993,7 +3118,7 @@
     <!-- Allows applications to set the system time directly.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.SET_TIME"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows applications to set the system time zone directly.
          <p>Not for use by third-party applications.
@@ -3001,7 +3126,7 @@
     <permission android:name="android.permission.SET_TIME_ZONE"
         android:label="@string/permlab_setTimeZone"
         android:description="@string/permdesc_setTimeZone"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows telephony to suggest the time / time zone.
          <p>Not for use by third-party applications.
@@ -3115,7 +3240,7 @@
          as locale.
          <p>Protection level: signature|privileged|development -->
     <permission android:name="android.permission.CHANGE_CONFIGURATION"
-        android:protectionLevel="signature|privileged|development" />
+        android:protectionLevel="signature|privileged|development|role" />
 
     <!-- Allows an application to read or write the system settings.
 
@@ -3132,7 +3257,7 @@
     <permission android:name="android.permission.WRITE_SETTINGS"
         android:label="@string/permlab_writeSettings"
         android:description="@string/permdesc_writeSettings"
-        android:protectionLevel="signature|preinstalled|appop|pre23" />
+        android:protectionLevel="signature|preinstalled|appop|pre23|role" />
 
     <!-- Allows an application to modify the Google service map.
     <p>Not for use by third-party applications. -->
@@ -3149,6 +3274,12 @@
     <permission android:name="android.permission.READ_DEVICE_CONFIG"
         android:protectionLevel="signature|preinstalled" />
 
+    <!-- @SystemApi @hide Allows applications like settings to read system-owned
+     application-specific locale configs.
+     <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.READ_APP_SPECIFIC_LOCALES"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to monitor {@link android.provider.Settings.Config} access.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.MONITOR_DEVICE_CONFIG_ACCESS"
@@ -3371,6 +3502,23 @@
     <permission android:name="android.permission.UPDATE_FONTS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Allows an application to use the AttestationVerificationService.
+         @hide -->
+    <permission android:name="android.permission.USE_ATTESTATION_VERIFICATION_SERVICE"
+                android:protectionLevel="signature" />
+
+    <!-- Allows an application to export a AttestationVerificationService to verify attestations on
+         behalf of AttestationVerificationManager for system-defined attestation profiles.
+         @hide -->
+    <permission android:name="android.permission.VERIFY_ATTESTATION"
+                android:protectionLevel="signature" />
+
+    <!-- Must be required by any AttestationVerificationService to ensure that only the system can
+         bind to it.
+         @hide -->
+    <permission android:name="android.permission.BIND_ATTESTATION_VERIFICATION_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- ========================================= -->
     <!-- Permissions for special development tools -->
     <!-- ========================================= -->
@@ -3379,7 +3527,7 @@
     <!-- Allows an application to read or write the secure system settings.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.WRITE_SECURE_SETTINGS"
-        android:protectionLevel="signature|privileged|development" />
+        android:protectionLevel="signature|privileged|development|role|installer" />
 
     <!-- Allows an application to retrieve state dump information from system services.
     <p>Not for use by third-party applications. -->
@@ -3434,6 +3582,16 @@
     <permission android:name="android.permission.REQUEST_INCIDENT_REPORT_APPROVAL"
         android:protectionLevel="signature|privileged" />
 
+    <!-- ========================================= -->
+    <!-- Permissions for SupplementalApi -->
+    <!-- ========================================= -->
+    <eat-comment />
+
+    <!-- TODO(b/213488783): Update with correct names. -->
+    <!-- Allows an application to access SupplementalApis. -->
+    <permission android:name="android.permission.ACCESS_SUPPLEMENTAL_APIS"
+        android:protectionLevel="normal" />
+
     <!-- ==================================== -->
     <!-- Private permissions                  -->
     <!-- ==================================== -->
@@ -3511,6 +3669,13 @@
     <permission android:name="android.permission.GET_APP_OPS_STATS"
         android:protectionLevel="signature|privileged|development" />
 
+    <!-- @SystemApi @hide Allows an application to collect historical application operation
+         statistics.
+         <p>Not for use by third party applications.
+    -->
+    <permission android:name="android.permission.GET_HISTORICAL_APP_OPS_STATS"
+        android:protectionLevel="internal|role" />
+
     <!-- @SystemApi Allows an application to update application operation statistics. Not for
          use by third party apps.
          @hide -->
@@ -3632,7 +3797,7 @@
          to put the higher-level system there into a shutdown state.
          @hide -->
     <permission android:name="android.permission.SHUTDOWN"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to tell the activity manager to temporarily
          stop application switches, putting it into a special mode that
@@ -3650,6 +3815,13 @@
     <permission android:name="android.permission.GET_TOP_ACTIVITY_INFO"
         android:protectionLevel="signature|recents" />
 
+    <!-- @SystemApi Allows an application to set the system audio caption and its UI
+         enabled state.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.SET_SYSTEM_AUDIO_CAPTION"
+                android:protectionLevel="signature|role" />
+
     <!-- Allows an application to retrieve the current state of keys and
          switches.
          <p>Not for use by third-party applications.
@@ -3786,6 +3958,15 @@
     <permission android:name="android.permission.BIND_WALLPAPER"
         android:protectionLevel="signature|privileged" />
 
+    <!-- Must be required by a game service to ensure that only the
+         system can bind to it.
+         <p>Protection level: signature
+         @SystemApi
+         @hide
+    -->
+    <permission android:name="android.permission.BIND_GAME_SERVICE"
+        android:protectionLevel="signature" />
+
     <!-- Must be required by a {@link android.service.voice.VoiceInteractionService},
          to ensure that only the system can bind to it.
          <p>Protection level: signature
@@ -3846,6 +4027,14 @@
     <permission android:name="android.permission.BIND_TEXTCLASSIFIER_SERVICE"
                 android:protectionLevel="signature" />
 
+    <!-- Must be required by a android.service.selectiontoolbar.SelectionToolbarRenderService,
+         to ensure that only the system can bind to it.
+         @hide This is not a third-party API (intended for OEMs and system apps).
+         <p>Protection level: signature
+    -->
+    <permission android:name="android.permission.BIND_SELECTION_TOOLBAR_RENDER_SERVICE"
+                android:protectionLevel="signature" />
+
     <!-- Must be required by a android.service.contentcapture.ContentCaptureService,
          to ensure that only the system can bind to it.
          @SystemApi @hide This is not a third-party API (intended for OEMs and system apps).
@@ -4003,14 +4192,13 @@
          <p>Protection level: signature
     -->
     <permission android:name="android.permission.BIND_DEVICE_ADMIN"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- @SystemApi Required to add or remove another application as a device admin.
          <p>Not for use by third-party applications.
-         @hide
-         @removed -->
+         @hide -->
     <permission android:name="android.permission.MANAGE_DEVICE_ADMINS"
-        android:protectionLevel="signature" />
+        android:protectionLevel="signature|role" />
 
     <!-- @SystemApi Allows an app to reset the device password.
          <p>Not for use by third-party applications.
@@ -4060,8 +4248,16 @@
     <permission android:name="android.permission.SCHEDULE_PRIORITIZED_ALARM"
                 android:protectionLevel="signature|privileged"/>
 
-    <!-- Allows an app to use exact alarm scheduling APIs to perform timing
-         sensitive background work.
+    <!-- Allows applications to use exact alarm APIs.
+         <p>Exact alarms should only be used for user-facing features.
+         For more details, see <a
+         href="{@docRoot}about/versions/12/behavior-changes-12#exact-alarm-permission">
+         Exact alarm permission</a>.
+         <p>Apps who hold this permission and target API level 31 or above, always stay in the
+         {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_WORKING_SET WORKING_SET} or
+         lower standby bucket.
+         Applications targeting API level 30 or below do not need this permission to use
+         exact alarm APIs.
      -->
     <permission android:name="android.permission.SCHEDULE_EXACT_ALARM"
         android:protectionLevel="normal|appop"/>
@@ -4115,14 +4311,14 @@
     <permission android:name="android.permission.INSTALL_PACKAGE_UPDATES"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to install existing system packages. This is a limited
+    <!-- @SystemApi Allows an application to install existing system packages. This is a limited
          version of {@link android.Manifest.permission#INSTALL_PACKAGES}.
          <p>Not for use by third-party applications.
          TODO(b/80204953): remove this permission once we have a long-term solution.
          @hide
     -->
     <permission android:name="com.android.permission.INSTALL_EXISTING_PACKAGES"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows an application to use the package installer v2 APIs.
          <p>The package installer v2 APIs are still a work in progress and we're
@@ -4140,6 +4336,16 @@
     <permission android:name="android.permission.INSTALL_TEST_ONLY_PACKAGE"
                 android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to install DPCs only, an application is
+         considered a DPC if it has a {@link android.app.admin.DeviceAdminReceiver}
+         protected by {@link android.Manifest.permission#BIND_DEVICE_ADMIN).
+         This is a limited version of
+         {@link android.Manifest.permission#INSTALL_PACKAGES}.
+         @hide
+    -->
+    <permission android:name="android.permission.INSTALL_DPC_PACKAGES"
+                android:protectionLevel="signature|role" />
+
     <!-- Allows an application to use System Data Loaders.
          <p>Not for use by third-party applications.
          @hide
@@ -4207,7 +4413,7 @@
          when the application deleting the package is not the same application that installed the
          package. -->
     <permission android:name="android.permission.DELETE_PACKAGES"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to move location of installed package.
          @hide -->
@@ -4223,7 +4429,7 @@
          enabled or not.
          <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to grant specific permissions.
          @hide -->
@@ -4241,6 +4447,12 @@
     <permission android:name="android.permission.REVOKE_RUNTIME_PERMISSIONS"
          android:protectionLevel="signature|installer|verifier" />
 
+    <!-- @TestApi Allows an application to revoke the POST_NOTIFICATIONS permission from an app
+     without killing the app. Only granted to the shell.
+     @hide -->
+    <permission android:name="android.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows the system to read runtime permission state.
         @hide -->
     <permission android:name="android.permission.GET_RUNTIME_PERMISSIONS"
@@ -4314,6 +4526,11 @@
     <permission android:name="android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE"
                 android:protectionLevel="normal" />
 
+    <!-- Allows an application to deliver companion messages to system
+         -->
+    <permission android:name="android.permission.DELIVER_COMPANION_MESSAGES"
+                android:protectionLevel="normal" />
+
     <!-- Allows an application to create new companion device associations.
          @SystemApi
          @hide -->
@@ -4436,6 +4653,12 @@
     <permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to modify the user preferred display mode.
+         @hide
+         @TestApi -->
+    <permission android:name="android.permission.MODIFY_USER_PREFERRED_DISPLAY_MODE"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -4506,6 +4729,12 @@
     <permission android:name="android.permission.CAPTURE_AUDIO_HOTWORD"
         android:protectionLevel="signature|privileged|role" />
 
+    <!-- @SystemApi Allows an application to access the ultrasound content.
+         <p>Not for use by third-party applications.</p>
+         @hide -->
+    <permission android:name="android.permission.ACCESS_ULTRASOUND"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Puts an application in the chain of trust for sound trigger
          operations. Being in the chain of trust allows an application to
          delegate an identity of a separate entity to the sound trigger system
@@ -4521,6 +4750,13 @@
     <permission android:name="android.permission.MODIFY_AUDIO_ROUTING"
         android:protectionLevel="signature|privileged|role" />
 
+    <!-- @SystemApi Allows an application to access the uplink and downlink audio of an ongoing
+    call.
+     <p>Not for use by third-party applications.</p>
+     @hide -->
+    <permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @TestApi Allows an application to query audio related state.
          @hide -->
     <permission android:name="android.permission.QUERY_AUDIO_STATE"
@@ -4664,7 +4900,7 @@
 
     <!-- Not for use by third-party applications. -->
     <permission android:name="android.permission.MASTER_CLEAR"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- Allows an application to call any phone number, including emergency
          numbers, without going through the Dialer user interface for the user
@@ -4675,7 +4911,7 @@
 
     <!-- @SystemApi Allows an application to perform CDMA OTA provisioning @hide -->
     <permission android:name="android.permission.PERFORM_CDMA_PROVISIONING"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to perform SIM Activation @hide -->
     <permission android:name="android.permission.PERFORM_SIM_ACTIVATION"
@@ -4724,6 +4960,12 @@
     <permission android:name="android.permission.CHANGE_APP_IDLE_STATE"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @hide @TestApi @SystemApi Allows an application to change the estimated launch time
+         of an app.
+         <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.CHANGE_APP_LAUNCH_TIME_ESTIMATE"
+        android:protectionLevel="signature|privileged" />
+
     <!-- @hide @SystemApi Allows an application to temporarily allowlist an inactive app to
          access the network and acquire wakelocks.
          <p>Not for use by third-party applications. -->
@@ -4902,7 +5144,7 @@
         @hide
     -->
     <permission android:name="android.permission.CRYPT_KEEPER"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|role" />
 
     <!-- @SystemApi Allows an application to read historical network usage for
          specific networks and applications. @hide -->
@@ -5047,10 +5289,10 @@
                 android:protectionLevel="signature|installer" />
     <uses-permission android:name="android.permission.MANAGE_NOTIFICATION_LISTENERS" />
 
-    <!-- Allows notifications to be colorized
+    <!-- @SystemApi Allows notifications to be colorized
          <p>Not for use by third-party applications. @hide -->
     <permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"
-                android:protectionLevel="signature|setup" />
+                android:protectionLevel="signature|setup|role" />
 
     <!-- Allows access to keyguard secure storage.  Only allowed for system processes.
         @hide -->
@@ -5103,6 +5345,12 @@
     <permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"
         android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to manage weak escrow token on the device. This permission
+         is not available to third party applications.
+         @hide -->
+    <permission android:name="android.permission.MANAGE_WEAK_ESCROW_TOKEN"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to listen to trust changes.  Only allowed for system processes.
         @hide -->
     <permission android:name="android.permission.TRUST_LISTENER"
@@ -5261,6 +5509,25 @@
         android:description="@string/permdesc_startViewPermissionUsage"
         android:protectionLevel="signature|installer" />
 
+    <!--
+        @SystemApi
+        Allows the holder to start the screen to review permission decisions.
+        <p>Protection level: signature|installer
+        @hide -->
+    <permission android:name="android.permission.START_REVIEW_PERMISSION_DECISIONS"
+        android:label="@string/permlab_startReviewPermissionDecisions"
+        android:description="@string/permdesc_startReviewPermissionDecisions"
+        android:protectionLevel="signature|installer" />
+
+    <!--
+        Allows the holder to start the screen with a list of app features.
+        <p>Protection level: signature|installer
+    -->
+    <permission android:name="android.permission.START_VIEW_APP_FEATURES"
+                android:label="@string/permlab_startViewAppFeatures"
+                android:description="@string/permdesc_startViewAppFeatures"
+                android:protectionLevel="signature|installer" />
+
     <!-- Allows an application to query whether DO_NOT_ASK_CREDENTIALS_ON_BOOT
          flag is set.
          @hide -->
@@ -5282,7 +5549,7 @@
     <!-- @SystemApi Allows access to MAC addresses of WiFi and Bluetooth peer devices.
         @hide -->
     <permission android:name="android.permission.PEERS_MAC_ADDRESS"
-                android:protectionLevel="signature|setup" />
+                android:protectionLevel="signature|setup|role" />
 
     <!-- Allows the Nfc stack to dispatch Nfc messages to applications. Applications
         can use this permission to ensure incoming Nfc messages are from the Nfc stack
@@ -5469,6 +5736,11 @@
     <permission android:name="android.permission.MANAGE_SMARTSPACE"
         android:protectionLevel="signature" />
 
+    <!-- @SystemApi Allows an application to manage the cloudsearch service.
+     @hide  <p>Not for use by third-party applications.</p> -->
+    <permission android:name="android.permission.MANAGE_CLOUDSEARCH"
+        android:protectionLevel="signature" />
+
     <!-- Allows an app to set the theme overlay in /vendor/overlay
          being used.
          @hide  <p>Not for use by third-party applications.</p> -->
@@ -5581,11 +5853,11 @@
     <permission android:name="android.permission.GRANT_PROFILE_OWNER_DEVICE_IDS_ACCESS"
         android:protectionLevel="signature" />
 
-    <!-- Allows an app to mark a profile owner as managing an organization-owned device.
+    <!-- @SystemApi Allows an app to mark a profile owner as managing an organization-owned device.
          <p>Not for use by third-party applications.
          @hide -->
     <permission android:name="android.permission.MARK_DEVICE_ORGANIZATION_OWNED"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|role" />
 
     <!-- Allows financial apps to read filtered sms messages.
          Protection level: signature|appop
@@ -5622,7 +5894,7 @@
     <!-- @SystemApi Allows sensor privacy to be modified.
          @hide -->
     <permission android:name="android.permission.MANAGE_SENSOR_PRIVACY"
-                android:protectionLevel="internal|role" />
+                android:protectionLevel="internal|role|installer" />
 
     <!-- @SystemApi Allows sensor privacy changes to be observed.
          @hide -->
@@ -5681,6 +5953,8 @@
     <!-- Allows query of any normal app on the device, regardless of manifest declarations.
         <p>Protection level: normal -->
     <permission android:name="android.permission.QUERY_ALL_PACKAGES"
+                android:label="@string/permlab_queryAllPackages"
+                android:description="@string/permdesc_queryAllPackages"
                 android:protectionLevel="normal" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
 
@@ -5703,10 +5977,20 @@
     <permission android:name="android.permission.ACCESS_TV_DESCRAMBLER"
         android:protectionLevel="signature|privileged|vendorPrivileged" />
 
+    <!-- @SystemApi Allows an application to access shared filter of TV tuner HAL
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.ACCESS_TV_SHARED_FILTER"
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
+
     <!-- Allows an application to create trusted displays. @hide -->
     <permission android:name="android.permission.ADD_TRUSTED_DISPLAY"
                 android:protectionLevel="signature" />
 
+    <!-- Allows an application to create always-unlocked displays. @hide -->
+    <permission android:name="android.permission.ADD_ALWAYS_UNLOCKED_DISPLAY"
+                android:protectionLevel="signature"/>
+
     <!-- @hide @SystemApi Allows an application to access locusId events in the usage stats. -->
     <permission android:name="android.permission.ACCESS_LOCUS_ID_USAGE_STATS"
                 android:protectionLevel="signature|role" />
@@ -5770,11 +6054,10 @@
     <permission android:name="android.permission.RENOUNCE_PERMISSIONS"
                 android:protectionLevel="signature|privileged" />
 
-    <!-- Allows an application to read nearby streaming policy. The policy allows the device
-         to stream its notifications and apps to nearby devices.
-         @hide -->
+    <!-- Allows an application to read nearby streaming policy. The policy controls
+         whether to allow the device to stream its notifications and apps to nearby devices. -->
     <permission android:name="android.permission.READ_NEARBY_STREAMING_POLICY"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="normal" />
 
     <!-- @SystemApi Allows the holder to set the source of the data when setting a clip on the
          clipboard.
@@ -5820,6 +6103,47 @@
     <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
                 android:protectionLevel="internal|role" />
 
+    <!-- @SystemApi Allows an application to create virtual devices in VirtualDeviceManager.
+         @hide -->
+    <permission android:name="android.permission.CREATE_VIRTUAL_DEVICE"
+                android:protectionLevel="internal|role" />
+
+    <!-- @SystemApi Must be required by a safety source to send an update using the
+             {@link android.safetycenter.SafetyCenterManager}.
+             <p>Protection level: signature|privileged
+             @hide
+        -->
+    <permission android:name="android.permission.SEND_SAFETY_CENTER_UPDATE"
+                android:protectionLevel="signature|privileged" />
+
+    <!-- Allows an application to launch device manager setup screens.
+         <p>Not for use by third-party applications.
+         @hide
+    -->
+    <permission android:name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP"
+        android:protectionLevel="signature|role" />
+
+    <!-- @SystemApi Allows an application to update certain device management related system
+         resources.
+         @hide -->
+    <permission android:name="android.permission.UPDATE_DEVICE_MANAGEMENT_RESOURCES"
+                android:protectionLevel="signature|role" />
+
+    <!-- @SystemApi Allows an app to read whether SafetyCenter is enabled/disabled.
+             <p>Protection level: signature|privileged
+             @hide
+        -->
+    <permission android:name="android.permission.READ_SAFETY_CENTER_STATUS"
+        android:protectionLevel="signature|privileged" />
+
+    <!-- @SystemApi Required to access the safety center internal APIs using the
+             {@link android.safetycenter.SafetyCenterManager}.
+             <p>Protection level: internal|installer|role
+             @hide
+        -->
+    <permission android:name="android.permission.MANAGE_SAFETY_CENTER"
+                android:protectionLevel="internal|installer|role" />
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -5877,7 +6201,6 @@
                   android:excludeFromRecents="true"
                   android:documentLaunchMode="never"
                   android:relinquishTaskIdentity="true"
-                  android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
                   android:process=":ui"
                   android:visibleToInstantApps="true">
             <intent-filter>
@@ -5928,8 +6251,8 @@
                 android:process=":ui">
         </activity>
         <activity android:name="com.android.internal.app.PlatLogoActivity"
-                android:theme="@style/Theme.DeviceDefault.DayNight"
-                android:configChanges="orientation|keyboardHidden"
+                android:theme="@style/Theme.DeviceDefault.Wallpaper.NoTitleBar"
+                android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
                 android:icon="@drawable/platlogo"
                 android:process=":ui">
         </activity>
@@ -6051,6 +6374,12 @@
                 android:process=":ui">
         </activity>
 
+        <activity android:name="com.android.internal.app.LaunchAfterAuthenticationActivity"
+                  android:theme="@style/Theme.Translucent.NoTitleBar"
+                  android:excludeFromRecents="true"
+                  android:process=":ui">
+        </activity>
+
         <activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
                   android:theme="@style/Theme.Dialog.Confirmation"
                   android:excludeFromRecents="true">
@@ -6238,7 +6567,7 @@
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
 
-        <service android:name="com.android.server.pm.BackgroundDexOptService"
+        <service android:name="com.android.server.pm.BackgroundDexOptJobService"
                  android:exported="true"
                  android:permission="android.permission.BIND_JOB_SERVICE">
         </service>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 35ed018..fbe7a44 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -22,12 +22,14 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
+import android.os.Process;
 import android.platform.test.annotations.AppModeFull;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -39,6 +41,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.xmlpull.v1.XmlPullParser;
@@ -88,6 +91,18 @@
             InstrumentationRegistry.getInstrumentation().getTargetContext();
 
     @Test
+    public void shellIsOnlySystemAppThatRequestsRevokePostNotificationsWithoutKill() {
+        List<PackageInfo> pkgs = sContext.getPackageManager().getInstalledPackages(
+                PackageManager.PackageInfoFlags.of(
+                PackageManager.GET_PERMISSIONS | PackageManager.MATCH_ALL));
+        int shellUid = Process.myUserHandle().getUid(Process.SHELL_UID);
+        for (PackageInfo pkg : pkgs) {
+            Assert.assertFalse(pkg.applicationInfo.uid != shellUid
+                    && hasRevokeNotificationNoKillPermission(pkg));
+        }
+    }
+
+    @Test
     public void platformPermissionPolicyIsUnaltered() throws Exception {
         Map<String, PermissionInfo> declaredPermissionsMap =
                 getPermissionsForPackage(sContext, PLATFORM_PACKAGE_NAME);
@@ -236,6 +251,20 @@
         assertWithMessage("list of offending permissions").that(offendingList).isEmpty();
     }
 
+    private boolean hasRevokeNotificationNoKillPermission(PackageInfo info) {
+        if (info.requestedPermissions == null) {
+            return false;
+        }
+
+        for (int i = 0; i < info.requestedPermissions.length; i++) {
+            if (Manifest.permission.REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL.equals(
+                    info.requestedPermissions[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private List<ExpectedPermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
         List<ExpectedPermissionInfo> permissions = new ArrayList<>();
         try (InputStream in = sContext.getResources().openRawResource(resourceId)) {
@@ -383,9 +412,6 @@
                 case "incidentReportApprover": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_INCIDENT_REPORT_APPROVER;
                 } break;
-                case "documenter": {
-                    protectionLevel |= PermissionInfo.PROTECTION_FLAG_DOCUMENTER;
-                } break;
                 case "appPredictor": {
                     protectionLevel |= PermissionInfo.PROTECTION_FLAG_APP_PREDICTOR;
                 } break;
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index dba2522..a1eca49 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -31,6 +31,7 @@
 import android.Manifest.permission.GET_ACCOUNTS
 import android.Manifest.permission.NEARBY_WIFI_DEVICES
 import android.Manifest.permission.PACKAGE_USAGE_STATS
+import android.Manifest.permission.POST_NOTIFICATIONS
 import android.Manifest.permission.PROCESS_OUTGOING_CALLS
 import android.Manifest.permission.READ_CALENDAR
 import android.Manifest.permission.READ_CALL_LOG
@@ -157,6 +158,7 @@
 
         // Add runtime permissions added in T which were _not_ split from a previously existing
         // runtime permission
+        expectedPerms.add(POST_NOTIFICATIONS)
         expectedPerms.add(NEARBY_WIFI_DEVICES)
 
         assertThat(expectedPerms).containsExactlyElementsIn(platformRuntimePerms.map { it.name })
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 3d91b81..75ef5b3 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -47,11 +47,17 @@
         ":CtsUsePermissionApp30",
         ":CtsUsePermissionApp30WithBackground",
         ":CtsUsePermissionApp30WithBluetooth",
+        ":CtsUsePermissionApp31",
+        ":CtsUsePermissionApp32",
         ":CtsUsePermissionAppLatest",
         ":CtsUsePermissionAppLatestNone",
         ":CtsUsePermissionAppWithOverlay",
         ":CtsAccessMicrophoneApp",
         ":CtsAccessMicrophoneApp2",
+        ":CtsAccessMicrophoneAppLocationProvider",
+        ":CtsHelperAppOverlay",
+        ":CtsCreateNotificationChannelsApp31",
+        ":CtsCreateNotificationChannelsApp33",
     ],
     test_suites: [
         "cts",
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index d708d76..129cb4d 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="test-suite-tag" value="cts" />
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
@@ -41,6 +42,7 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAccessMicrophoneApp.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp.apk" />
         <option name="push" value="CtsAccessMicrophoneApp2.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp2.apk" />
+        <option name="push" value="CtsAccessMicrophoneAppLocationProvider.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneAppLocationProvider.apk" />
         <option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
         <option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
         <option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
@@ -53,9 +55,14 @@
         <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
         <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
         <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" />
+        <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" />
+        <option name="push" value="CtsUsePermissionApp32.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp32.apk" />
         <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
         <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
         <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
+        <option name="push" value="CtsHelperAppOverlay.apk->/data/local/tmp/cts/permission3/CtsHelperAppOverlay.apk" />
+        <option name="push" value="CtsCreateNotificationChannelsApp31.apk->/data/local/tmp/cts/permission3/CtsCreateNotificationChannelsApp31.apk" />
+        <option name="push" value="CtsCreateNotificationChannelsApp33.apk->/data/local/tmp/cts/permission3/CtsCreateNotificationChannelsApp33.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp b/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp
new file mode 100644
index 0000000..265a01c
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsCreateNotificationChannelsApp31",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "31",
+
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml b/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml
new file mode 100644
index 0000000..d3d73a0
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.permission3.cts.usepermission"
+          android:versionCode="1">
+
+    <uses-sdk android:minSdkVersion="31" android:targetSdkVersion="31" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+    <application android:label="CreateNotificationChannels">
+        <activity android:name=".CreateNotificationChannelsActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="usepermission.createchannels.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt b/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
new file mode 100644
index 0000000..9e09ad1
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp31/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.permission3.cts.usepermission
+
+import android.Manifest
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.Looper
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_CREATE_CHANNELS_DELAYED = "extra_create_delayed"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val CHANNEL_ID = "channel_id"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val DELAY_MS = 1000L
+
+class CreateNotificationChannelsActivity : Activity() {
+    lateinit var notificationManager: NotificationManager
+    override fun onStart() {
+        val handler = Handler(Looper.getMainLooper())
+        notificationManager = baseContext.getSystemService(NotificationManager::class.java)!!
+        if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS, false)) {
+            if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+                notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+                    "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+            }
+        } else if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS_DELAYED, false)) {
+            handler.postDelayed({
+                if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+                    notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+                        "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+                }
+            }, DELAY_MS)
+        }
+
+        if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS, false)) {
+            requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+        } else if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, false)) {
+            handler.postDelayed({
+                requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+            }, DELAY_MS)
+        }
+        super.onStart()
+    }
+
+    override fun onPause() {
+        if (intent.getBooleanExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, false)) {
+            notificationManager.deleteNotificationChannel(CHANNEL_ID)
+        }
+        super.onPause()
+    }
+
+    override fun onRequestPermissionsResult(
+        requestCode: Int,
+        permissions: Array<out String>,
+        grantResults: IntArray
+    ) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
+        val grantedPerms = arrayListOf<String>()
+        for ((i, permName) in permissions.withIndex()) {
+            if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
+                grantedPerms.add(permName)
+            }
+        }
+        sendBroadcast(
+            Intent(BROADCAST_ACTION).putStringArrayListExtra(
+            PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantedPerms))
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp b/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp
new file mode 100644
index 0000000..67bf182
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsCreateNotificationChannelsApp33",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "33",
+
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+
+    srcs: [
+        "src/**/*.kt",
+    ],
+}
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml b/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml
new file mode 100644
index 0000000..c302116
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.permission3.cts.usepermission"
+          android:versionCode="1">
+
+    <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+
+    <application android:label="CreateNotificationChannels">
+        <activity android:name=".CreateNotificationChannelsActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="usepermission.createchannels.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt b/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
new file mode 100644
index 0000000..e9550d1
--- /dev/null
+++ b/tests/tests/permission3/CreateNotificationChannelsApp33/src/android/permission3/cts/usepermission/CreateNotificationChannelsActivity.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.permission3.cts.usepermission
+
+import android.Manifest
+import android.app.Activity
+import android.app.NotificationChannel
+import android.app.NotificationManager
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val CHANNEL_ID = "channel_id"
+const val DELAY_MS = 1000L
+
+class CreateNotificationChannelsActivity : Activity() {
+    lateinit var notificationManager: NotificationManager
+    override fun onStart() {
+        notificationManager = baseContext.getSystemService(NotificationManager::class.java)!!
+        if (intent.getBooleanExtra(EXTRA_CREATE_CHANNELS, false)) {
+            if (notificationManager.getNotificationChannel(CHANNEL_ID) == null) {
+                notificationManager.createNotificationChannel(NotificationChannel(CHANNEL_ID,
+                        "Foreground Services", NotificationManager.IMPORTANCE_HIGH))
+            }
+        }
+
+        if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS, false)) {
+            requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+        } else if (intent.getBooleanExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, false)) {
+            mainThreadHandler.postDelayed({
+                requestPermissions(arrayOf(Manifest.permission.RECORD_AUDIO), 0)
+            }, DELAY_MS)
+        }
+
+        super.onStart()
+    }
+
+    override fun onPause() {
+        if (intent.getBooleanExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, false)) {
+            notificationManager.deleteNotificationChannel(CHANNEL_ID)
+        }
+        super.onPause()
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/HelperAppOverlay/Android.bp b/tests/tests/permission3/HelperAppOverlay/Android.bp
new file mode 100644
index 0000000..94d9653
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsHelperAppOverlay",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml b/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..04d5a4b
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission3.cts.helper.overlay">
+
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <application>
+        <activity android:name=".OverlayActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt b/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt
new file mode 100644
index 0000000..2c1497f
--- /dev/null
+++ b/tests/tests/permission3/HelperAppOverlay/src/android/permission3/cts/helper/overlay/OverlayActivity.kt
@@ -0,0 +1,26 @@
+package android.permission3.cts.helper.overlay
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.WindowManager
+import android.widget.LinearLayout
+import android.widget.TextView
+
+class OverlayActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val mainLayout = LinearLayout(this)
+        mainLayout.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+        val textView = TextView(this)
+
+        textView.text = "Find me!"
+        mainLayout.addView(textView)
+
+        val windowParams = WindowManager.LayoutParams()
+        windowParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
+        windowManager.addView(mainLayout, windowParams)
+    }
+}
diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp
new file mode 100644
index 0000000..48a2d4f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/Android.bp
@@ -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.
+//
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp31",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+    target_sdk_version: "31",
+    min_sdk_version: "31",
+}
diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
new file mode 100644
index 0000000..dfa12b7
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/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.permission3.cts.usepermission">
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionApp32/Android.bp b/tests/tests/permission3/UsePermissionApp32/Android.bp
new file mode 100644
index 0000000..adf24cf
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp32/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp32",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+
+    min_sdk_version: "32",
+    target_sdk_version: "32",
+}
diff --git a/tests/tests/permission3/UsePermissionApp32/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp32/AndroidManifest.xml
new file mode 100644
index 0000000..35df6c8
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp32/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="android.permission3.cts.usepermission">
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
+    <application>
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
index 1d8cdf3..0c125d0 100644
--- a/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
+++ b/tests/tests/permission3/UsePermissionAppLatest/AndroidManifest.xml
@@ -25,7 +25,7 @@
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
-
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
     <application>
         <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
         <activity android:name=".FinishOnCreateActivity" android:exported="true" />
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
new file mode 100644
index 0000000..a2cd88d
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAccessMicrophoneAppLocationProvider",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "31",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..56afc09
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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.permission3.cts.accessmicrophoneapplocationprovider">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <attribution
+        android:label="@string/attribution_label"
+        android:tag="test.tag" />
+
+    <application
+        android:attributionsAreUserVisible="true"
+        android:label="LocationProviderWithMicApp">
+        <activity android:name=".AddLocationProviderActivity" 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=".UseMicrophoneActivity" android:exported="true"/>
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
new file mode 100644
index 0000000..9682d7f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.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.
+  -->
+
+<resources>
+    <string name="attribution_label">Attribution Label</string>
+</resources>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
new file mode 100644
index 0000000..3b3355c
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.location.Criteria
+import android.location.LocationManager
+import android.os.Bundle
+
+/**
+ * An activity that adds this package as a test location provider and uses microphone.
+ */
+class AddLocationProviderActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val attrContext = createAttributionContext("test.tag")
+        val locationManager = attrContext.getSystemService(LocationManager::class.java)
+        locationManager.addTestProvider(
+            packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
+            Criteria.ACCURACY_COARSE
+        )
+
+        setResult(RESULT_OK)
+        finish()
+    }
+}
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
new file mode 100644
index 0000000..0d8374f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.content.Context
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.os.Handler
+
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * An activity that uses microphone.
+ */
+class UseMicrophoneActivity : Activity() {
+    private var recorder: AudioRecord? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val attrContext = createAttributionContext("test.tag")
+        useMic(attrContext)
+        setResult(RESULT_OK)
+        finish()
+    }
+
+    override fun finish() {
+        recorder?.stop()
+        recorder = null
+        super.finish()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        finish()
+    }
+
+    private fun useMic(context: Context) {
+        recorder = AudioRecord.Builder()
+            .setAudioSource(MediaRecorder.AudioSource.MIC)
+            .setAudioFormat(AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setSampleRate(SAMPLE_RATE_HZ)
+                .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                .build())
+            .setContext(context)
+            .build()
+        recorder?.startRecording()
+        Handler().postDelayed({ finish() }, USE_DURATION_MS)
+    }
+}
diff --git a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
index 2f0b9fc..6dbd472 100644
--- a/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
+++ b/tests/tests/permission3/UsePermissionAppWithOverlay/src/android/permission3/cts/usepermission/OverlayActivity.kt
@@ -18,14 +18,15 @@
         params.flags = (WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS or
                 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
                 WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or
-                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
+                WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
 
         if (!intent.getBooleanExtra(EXTRA_FULL_OVERLAY, true)) {
             params.gravity = Gravity.LEFT or Gravity.TOP
-            val left = intent.getIntExtra(DIALOG_LEFT, params.x)
-            val top = intent.getIntExtra(DIALOG_TOP, params.y)
-            val right = intent.getIntExtra(DIALOG_RIGHT, params.x + params.width)
-            val bottom = intent.getIntExtra(MESSAGE_BOTTOM, top + 1)
+            val left = intent.getIntExtra(OVERLAY_LEFT, params.x)
+            val top = intent.getIntExtra(OVERLAY_TOP, params.y)
+            val right = intent.getIntExtra(OVERLAY_RIGHT, params.x + params.width)
+            val bottom = intent.getIntExtra(OVERLAY_BOTTOM, top + 1)
             params.x = left
             params.y = top
             params.width = right - left
@@ -46,9 +47,9 @@
     companion object {
         const val EXTRA_FULL_OVERLAY = "android.permission3.cts.usepermission.extra.FULL_OVERLAY"
 
-        const val DIALOG_LEFT = "android.permission3.cts.usepermission.extra.DIALOG_LEFT"
-        const val DIALOG_TOP = "android.permission3.cts.usepermission.extra.DIALOG_TOP"
-        const val DIALOG_RIGHT = "android.permission3.cts.usepermission.extra.DIALOG_RIGHT"
-        const val MESSAGE_BOTTOM = "android.permission3.cts.usepermission.extra.MESSAGE_BOTTOM"
+        const val OVERLAY_LEFT = "android.permission3.cts.usepermission.extra.OVERLAY_LEFT"
+        const val OVERLAY_TOP = "android.permission3.cts.usepermission.extra.OVERLAY_TOP"
+        const val OVERLAY_RIGHT = "android.permission3.cts.usepermission.extra.OVERLAY_RIGHT"
+        const val OVERLAY_BOTTOM = "android.permission3.cts.usepermission.extra.OVERLAY_BOTTOM"
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
new file mode 100644
index 0000000..014a4f3
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.permission3.cts
+
+import android.content.Intent
+import com.android.compatibility.common.util.SystemUtil
+
+/** Base class for the permission hub tests. */
+abstract class BasePermissionHubTest : BasePermissionTest() {
+
+    protected fun openMicrophoneTimeline() {
+        SystemUtil.runWithShellPermissionIdentity {
+            context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
+                putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME,
+                    android.Manifest.permission_group.MICROPHONE)
+                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index c0ee17b..3968fc3 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -27,6 +27,7 @@
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiDevice
 import android.support.test.uiautomator.UiObject2
+import android.text.Html
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.ActivityTestRule
 import com.android.compatibility.common.util.DisableAnimationRule
@@ -59,6 +60,10 @@
     private val mPermissionControllerResources: Resources = context.createPackageContext(
             context.packageManager.permissionControllerPackageName, 0).resources
 
+    protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+    protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+    protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+
     @get:Rule
     val disableAnimationRule = DisableAnimationRule()
 
@@ -99,11 +104,14 @@
         pressHome()
     }
 
-    protected fun getPermissionControllerString(res: String): Pattern =
-            Pattern.compile(Pattern.quote(mPermissionControllerResources.getString(
-                    mPermissionControllerResources.getIdentifier(
-                            res, "string", "com.android.permissioncontroller"))),
-                    Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+    protected fun getPermissionControllerString(res: String, vararg formatArgs: Any): Pattern {
+        val textWithHtml = mPermissionControllerResources.getString(
+                mPermissionControllerResources.getIdentifier(
+                        res, "string", "com.android.permissioncontroller"), *formatArgs)
+        val textWithoutHtml = Html.fromHtml(textWithHtml, 0).toString()
+        return Pattern.compile(Pattern.quote(textWithoutHtml),
+                Pattern.CASE_INSENSITIVE or Pattern.UNICODE_CASE)
+    }
 
     protected fun installPackage(
         apkPath: String,
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index 24ab5aa..8b63fb3 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -28,10 +28,8 @@
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiScrollable
 import android.support.test.uiautomator.UiSelector
-import android.support.test.uiautomator.StaleObjectException
 import android.text.Spanned
 import android.text.style.ClickableSpan
-import android.util.Log
 import android.view.View
 import com.android.compatibility.common.util.SystemUtil.eventually
 import org.junit.After
@@ -54,6 +52,9 @@
         const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
         const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
         const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+        const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk"
+        const val APP_APK_PATH_32 = "$APK_DIRECTORY/CtsUsePermissionApp32.apk"
+
         const val APP_APK_PATH_30_WITH_BACKGROUND =
                 "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
         const val APP_APK_PATH_30_WITH_BLUETOOTH =
@@ -61,6 +62,10 @@
         const val APP_APK_PATH_LATEST = "$APK_DIRECTORY/CtsUsePermissionAppLatest.apk"
         const val APP_APK_PATH_LATEST_NONE = "$APK_DIRECTORY/CtsUsePermissionAppLatestNone.apk"
         const val APP_APK_PATH_WITH_OVERLAY = "$APK_DIRECTORY/CtsUsePermissionAppWithOverlay.apk"
+        const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31 =
+            "$APK_DIRECTORY/CtsCreateNotificationChannelsApp31.apk"
+        const val APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33 =
+            "$APK_DIRECTORY/CtsCreateNotificationChannelsApp33.apk"
         const val APP_PACKAGE_NAME = "android.permission3.cts.usepermission"
 
         const val ALLOW_BUTTON =
@@ -76,6 +81,8 @@
                 "com.android.permissioncontroller:" +
                         "id/permission_no_upgrade_and_dont_ask_again_button"
 
+        const val ALLOW_ALWAYS_RADIO_BUTTON =
+                "com.android.permissioncontroller:id/allow_always_radio_button"
         const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button"
         const val ALLOW_FOREGROUND_RADIO_BUTTON =
                 "com.android.permissioncontroller:id/allow_foreground_only_radio_button"
@@ -91,6 +98,8 @@
         const val DENY_AND_DONT_ASK_AGAIN_BUTTON_TEXT =
                 "grant_dialog_button_deny_and_dont_ask_again"
         const val NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON_TEXT = "grant_dialog_button_no_upgrade"
+
+        const val REQUEST_LOCATION_MESSAGE = "permgrouprequest_location"
     }
 
     enum class PermissionState {
@@ -99,10 +108,6 @@
         DENIED_WITH_PREJUDICE
     }
 
-    protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-    protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
-    protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
-
     private val platformResources = context.createPackageContext("android", 0).resources
     private val permissionToLabelResNameMap = mapOf(
             // Contacts
@@ -132,6 +137,8 @@
                     to "@android:string/permgrouplab_location",
             android.Manifest.permission.ACCESS_COARSE_LOCATION
                     to "@android:string/permgrouplab_location",
+            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                    to "@android:string/permgrouplab_location",
             // Phone
             android.Manifest.permission.READ_PHONE_STATE
                     to "@android:string/permgrouplab_phone",
@@ -151,6 +158,8 @@
             android.Manifest.permission.CAMERA to "@android:string/permgrouplab_camera",
             // Body sensors
             android.Manifest.permission.BODY_SENSORS to "@android:string/permgrouplab_sensors",
+            android.Manifest.permission.BODY_SENSORS_BACKGROUND
+                    to "@android:string/permgrouplab_sensors",
             // Bluetooth
             android.Manifest.permission.BLUETOOTH_CONNECT to
                     "@android:string/permgrouplab_nearby_devices",
@@ -475,7 +484,7 @@
                     // Automotive doesn't support one time permissions, and thus
                     // won't show an "Ask every time" message
                     when (state) {
-                        PermissionState.ALLOWED ->By.text(
+                        PermissionState.ALLOWED -> By.text(
                                 getPermissionControllerString("app_permission_button_allow"))
                         PermissionState.DENIED -> By.text(
                                 getPermissionControllerString("app_permission_button_deny"))
@@ -505,6 +514,8 @@
                         PermissionState.ALLOWED ->
                             if (showsForegroundOnlyButton(permission)) {
                                 By.res(ALLOW_FOREGROUND_RADIO_BUTTON)
+                            } else if (showsAlwaysButton(permission)) {
+                                By.res(ALLOW_ALWAYS_RADIO_BUTTON)
                             } else if (isMediaStorageButton(permission, targetSdk)) {
                                 // Uses "allow_foreground_only_radio_button" as id
                                 byTextRes(R.string.allow_media_storage)
@@ -581,6 +592,12 @@
             else -> false
         }
 
+    private fun showsAlwaysButton(permission: String): Boolean =
+        when (permission) {
+            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+            else -> false
+        }
+
     private fun isMediaStorageButton(permission: String, targetSdk: Int): Boolean =
             if (isTv || isWatch) {
                 false
diff --git a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
index 1d8ed8e..e04b37b 100644
--- a/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/NoPermissionTest.kt
@@ -18,6 +18,8 @@
 
 import android.app.Activity
 import androidx.test.runner.AndroidJUnit4
+import com.android.modules.utils.build.SdkLevel
+import org.junit.Assume
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -25,6 +27,7 @@
 class NoPermissionTest : BaseUsePermissionTest() {
     @Test
     fun testStartActivity22() {
+        Assume.assumeFalse(SdkLevel.isAtLeastT())
         installPackage(APP_APK_PATH_22_NONE)
 
         startAppActivityAndAssertResultCode(Activity.RESULT_OK) {}
diff --git a/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
new file mode 100644
index 0000000..c72b97d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/NotificationPermissionTest.kt
@@ -0,0 +1,257 @@
+/*
+ * 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.permission3.cts
+
+import android.Manifest.permission.POST_NOTIFICATIONS
+import android.Manifest.permission.RECORD_AUDIO
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.os.Process
+import android.os.UserHandle
+import android.provider.Settings
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Ignore
+import org.junit.Test
+import java.util.concurrent.CountDownLatch
+
+const val EXTRA_DELETE_CHANNELS_ON_CLOSE = "extra_delete_channels_on_close"
+const val EXTRA_CREATE_CHANNELS = "extra_create"
+const val EXTRA_CREATE_CHANNELS_DELAYED = "extra_create_delayed"
+const val EXTRA_REQUEST_PERMISSIONS = "extra_request_permissions"
+const val EXTRA_REQUEST_PERMISSIONS_DELAYED = "extra_request_permissions_delayed"
+const val ACTIVITY_NAME = "CreateNotificationChannelsActivity"
+const val INTENT_ACTION = "usepermission.createchannels.MAIN"
+const val BROADCAST_ACTION = "usepermission.createchannels.BROADCAST"
+const val NOTIFICATION_PERMISSION_ENABLED = "notification_permission_enabled"
+const val DELAY_MS = 2000L
+
+class NotificationPermissionTest : BaseUsePermissionTest() {
+
+    private val cr = context.createContextAsUser(UserHandle.SYSTEM, 0).contentResolver
+    private var previousEnableState = 0
+    private var countDown: CountDownLatch = CountDownLatch(1)
+    private var allowedGroups = listOf<String>()
+    private val receiver: BroadcastReceiver = object : BroadcastReceiver() {
+        override fun onReceive(context: Context?, intent: Intent?) {
+            countDown.countDown()
+            allowedGroups = intent?.getStringArrayListExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) ?: emptyList()
+        }
+    }
+
+    @Before
+    fun setLatchAndEnablePermission() {
+        runWithShellPermissionIdentity {
+            previousEnableState = Settings.Secure.getInt(cr, NOTIFICATION_PERMISSION_ENABLED, 0)
+            Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, 1)
+        }
+        countDown = CountDownLatch(1)
+        allowedGroups = listOf()
+        context.registerReceiver(receiver, IntentFilter(BROADCAST_ACTION))
+    }
+
+    @After
+    fun resetPermissionAndRemoveReceiver() {
+        runWithShellPermissionIdentity {
+            Settings.Secure.putInt(cr, NOTIFICATION_PERMISSION_ENABLED, previousEnableState)
+        }
+        context.unregisterReceiver(receiver)
+    }
+
+    @Test
+    fun notificationPermissionAddedForLegacyApp() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        runWithShellPermissionIdentity {
+            Assert.assertTrue("SDK < 32 apps should have POST_NOTIFICATIONS added implicitly",
+                context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+                    PackageManager.GET_PERMISSIONS).requestedPermissions
+                    .contains(POST_NOTIFICATIONS))
+        }
+    }
+
+    // TODO ntmyren: enable when SDK 33 is a valid target
+    @Test
+    @Ignore
+    fun notificationPermissionIsNotImplicitlyAddedTo33Apps() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33, expectSuccess = true)
+        runWithShellPermissionIdentity {
+            Assert.assertFalse("SDK < 33 apps should NOT have POST_NOTIFICATIONS added implicitly",
+                context.packageManager.getPackageInfo(APP_PACKAGE_NAME,
+                    PackageManager.GET_PERMISSIONS).requestedPermissions
+                    .contains(POST_NOTIFICATIONS))
+        }
+    }
+
+    // TODO ntmyren: enable when SDK 33 is a valid target
+    @Test
+    @Ignore
+    fun reviewRequiredClearedForTAppsOnLaunch() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_33, expectSuccess = true)
+        runWithShellPermissionIdentity {
+            context.packageManager.updatePermissionFlags(POST_NOTIFICATIONS, APP_PACKAGE_NAME,
+                FLAG_PERMISSION_REVIEW_REQUIRED, FLAG_PERMISSION_REVIEW_REQUIRED,
+                Process.myUserHandle())
+        }
+        launchApp()
+        waitForIdle()
+        assertNotificationReviewRequiredCleared()
+    }
+
+    @Test
+    fun notificationPromptShowsForLegacyAppAfterCreatingNotificationChannels() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp()
+        waitForIdle()
+        clickPermissionRequestAllowButton()
+    }
+
+    @Test
+    fun notificationPromptShowsForLegacyAppWithNotificationChannelsOnStart() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp()
+        waitForIdle()
+        pressBack()
+        pressBack()
+        waitForIdle()
+        launchApp()
+        clickPermissionRequestAllowButton()
+    }
+
+    @Test
+    fun notificationPromptDoesNotShowForLegacyAppWithNoNotificationChannels() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp(createChannels = false)
+        try {
+            clickPermissionRequestAllowButton()
+            Assert.fail("Expected not to find permission request dialog")
+        } catch (expected: RuntimeException) {
+            // Do nothing
+        }
+    }
+
+    @Test
+    fun notificationGrantedAndReviewRequiredClearedOnLegacyGrant() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp()
+        clickPermissionRequestAllowButton()
+        assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+        assertNotificationReviewRequiredCleared()
+    }
+
+    @Test
+    fun notificationReviewRequiredClearedOnLegacyDeny() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp()
+        clickPermissionRequestDenyButton()
+        assertNotificationReviewRequiredCleared()
+    }
+
+    @Test
+    fun ensureNonSystemServerPackageCannotShowPromptForOtherPackage() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        runWithShellPermissionIdentity {
+            val grantPermission = Intent(PackageManager.ACTION_REQUEST_PERMISSIONS_FOR_OTHER)
+            grantPermission.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
+            grantPermission.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES,
+                arrayOf(POST_NOTIFICATIONS))
+            grantPermission.setPackage(context.packageManager.permissionControllerPackageName)
+            grantPermission.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            context.startActivity(grantPermission)
+        }
+        try {
+            clickPermissionRequestAllowButton()
+            Assert.fail("Expected not to find permission request dialog")
+        } catch (expected: RuntimeException) {
+            // Do nothing
+        }
+    }
+
+    @Test
+    fun mergeAppPermissionRequestIntoNotificationAndVerifyResult() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp(requestPermissionsDelayed = true)
+        Thread.sleep(DELAY_MS)
+        clickPermissionRequestAllowButton()
+        assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+        clickPermissionRequestAllowForegroundButton()
+        assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+        countDown.await()
+        // Result should contain only the microphone request
+        Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+    }
+
+    @Test
+    fun mergeNotificationRequestIntoAppPermissionRequestAndVerifyResult() {
+        installPackage(APP_APK_PATH_CREATE_NOTIFICATION_CHANNELS_31, expectSuccess = true)
+        launchApp(createChannels = false, createChannelsDelayed = true, requestPermissions = true)
+        Thread.sleep(DELAY_MS)
+        clickPermissionRequestAllowForegroundButton()
+        assertAppPermissionGrantedState(RECORD_AUDIO, granted = true)
+        clickPermissionRequestAllowButton()
+        assertAppPermissionGrantedState(POST_NOTIFICATIONS, granted = true)
+        countDown.await()
+        // Result should contain only the microphone request
+        Assert.assertEquals(listOf(RECORD_AUDIO), allowedGroups)
+    }
+
+    private fun assertAppPermissionGrantedState(permission: String, granted: Boolean) {
+        runWithShellPermissionIdentity {
+            Assert.assertEquals("Expected $permission to be granted", context.packageManager
+                .checkPermission(permission, APP_PACKAGE_NAME), PERMISSION_GRANTED)
+        }
+    }
+
+    private fun assertNotificationReviewRequiredCleared() {
+        runWithShellPermissionIdentity {
+            val permFlags = context.packageManager
+                .getPermissionFlags(POST_NOTIFICATIONS, APP_PACKAGE_NAME, Process.myUserHandle())
+            Assert.assertEquals("Expected REVIEW_REQUIRED to bel cleared for POST_NOTIFICATIONS",
+                permFlags and FLAG_PERMISSION_REVIEW_REQUIRED, 0)
+        }
+    }
+
+    private fun launchApp(
+        createChannels: Boolean = true,
+        createChannelsDelayed: Boolean = false,
+        deleteChannels: Boolean = false,
+        requestPermissions: Boolean = false,
+        requestPermissionsDelayed: Boolean = false
+    ) {
+        val intent = Intent(INTENT_ACTION)
+        intent.`package` = APP_PACKAGE_NAME
+        intent.putExtra(EXTRA_CREATE_CHANNELS, createChannels)
+        if (!createChannels) {
+            intent.putExtra(EXTRA_CREATE_CHANNELS_DELAYED, createChannelsDelayed)
+        }
+        intent.putExtra(EXTRA_DELETE_CHANNELS_ON_CLOSE, deleteChannels)
+        intent.putExtra(EXTRA_REQUEST_PERMISSIONS, requestPermissions)
+        if (!requestPermissions) {
+            intent.putExtra(EXTRA_REQUEST_PERMISSIONS_DELAYED, requestPermissionsDelayed)
+        }
+        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+
+        context.startActivity(intent)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
new file mode 100644
index 0000000..e88572d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -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.permission3.cts
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.AppOpsUtils.setOpMode
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests permission attribution for location providers.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class PermissionAttributionTest : BasePermissionHubTest() {
+    private val micLabel = packageManager.getPermissionGroupInfo(
+        android.Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+    val locationManager = context.getSystemService(LocationManager::class.java)!!
+    private var wasEnabled = false
+
+    // Permission history is not available on Auto devices running S or below.
+    @Before
+    fun assumeNotAutoBelowT() {
+        assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+    }
+
+    @Before
+    fun installAppLocationProviderAndAllowMockLocation() {
+        installPackage(APP_APK_PATH, grantRuntimePermissions = true)
+        // The package name of a mock location provider is the caller adding it, so we have to let
+        // the test app add itself.
+        setOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED)
+    }
+
+    @Before
+    fun setup() {
+        // Allow ourselves to reliably remove the test location provider.
+        setOpMode(
+            context.packageName, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED
+        )
+        wasEnabled = setSubattributionEnabledStateIfNeeded(true)
+    }
+
+    @After
+    fun teardown() {
+        locationManager.removeTestProvider(APP_PACKAGE_NAME)
+        if (!wasEnabled) {
+            setSubattributionEnabledStateIfNeeded(false)
+        }
+    }
+
+    @Test
+    fun testLocationProviderAttributionForMicrophone() {
+        enableAppAsLocationProvider()
+        useMicrophone()
+        openMicrophoneTimeline()
+
+        waitFindObject(By.descContains(micLabel))
+        waitFindObject(By.textContains(APP_LABEL))
+        waitFindObject(By.textContains(ATTRIBUTION_LABEL))
+    }
+
+    private fun enableAppAsLocationProvider() {
+        // Add the test app as location provider.
+        val future = startActivityForFuture(
+            Intent().apply {
+                component = ComponentName(
+                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.AddLocationProviderActivity"
+                )
+            }
+        )
+        val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        assertEquals(Activity.RESULT_OK, result.resultCode)
+        assertTrue(
+            callWithShellPermissionIdentity {
+                locationManager.isProviderPackage(APP_PACKAGE_NAME)
+            }
+        )
+    }
+
+    private fun useMicrophone() {
+        val future = startActivityForFuture(
+            Intent().apply {
+                component = ComponentName(
+                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.UseMicrophoneActivity"
+                )
+            }
+        )
+        val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        assertEquals(Activity.RESULT_OK, result.resultCode)
+    }
+
+    private fun setSubattributionEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+        var currentlyEnabled = false
+        runWithShellPermissionIdentity {
+            currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                FLAG_SUBATTRIBUTION, false)
+            if (currentlyEnabled != shouldBeEnabled) {
+                DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, FLAG_SUBATTRIBUTION,
+                    shouldBeEnabled.toString(), false)
+            }
+        }
+        return currentlyEnabled
+    }
+
+    companion object {
+        const val APP_APK_PATH = "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
+        const val APP_PACKAGE_NAME = "android.permission3.cts.accessmicrophoneapplocationprovider"
+        const val APP_LABEL = "LocationProviderWithMicApp"
+        const val ATTRIBUTION_LABEL = "Attribution Label"
+        const val FLAG_SUBATTRIBUTION = "permissions_hub_subattribution_enabled"
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
index 794a731..695d802 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
@@ -18,15 +18,17 @@
 
 import android.Manifest
 import android.content.Intent
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
 import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.DeviceConfig.NAMESPACE_PRIVACY
 import android.support.test.uiautomator.By
 import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.SystemUtil
+import com.android.modules.utils.build.SdkLevel
 import org.junit.After
 import org.junit.Assume.assumeFalse
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 
 private const val APP_LABEL_1 = "CtsMicAccess"
@@ -37,22 +39,28 @@
 private const val HISTORY_PREFERENCE_ICON = "permission_history_icon"
 private const val HISTORY_PREFERENCE_TIME = "permission_history_time"
 private const val SHOW_SYSTEM = "Show system"
+private const val SHOW_7_DAYS = "Show 7 days"
+private const val SHOW_24_HOURS = "Show 24 hours"
 private const val MORE_OPTIONS = "More options"
+private const val TIMELINE_7_DAYS_DESCRIPTION = "in the past 7 days"
+private const val DASHBOARD_7_DAYS_DESCRIPTION = "7 days"
+private const val PRIV_DASH_7_DAY_ENABLED = "privacy_dashboard_7_day_toggle"
 
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-class PermissionHistoryTest : BasePermissionTest() {
+class PermissionHistoryTest : BasePermissionHubTest() {
     private val micLabel = packageManager.getPermissionGroupInfo(
-            Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+        Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+    private var was7DayToggleEnabled = false
 
     // Permission history is not available on TV devices.
     @Before
-    fun assumeNotTv() =
-            assumeFalse(packageManager.hasSystemFeature(FEATURE_LEANBACK))
+    fun assumeNotTv() = assumeFalse(isTv)
 
-    // Permission history is not available on Auto devices.
+    // Permission history is not available on Auto devices running S or below.
     @Before
-    fun assumeNotAuto() =
-            assumeFalse(packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE))
+    fun assumeNotAutoBelowT() {
+        assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+    }
 
     @Before
     fun installApps() {
@@ -68,6 +76,24 @@
         uninstallPackage(APP2_PACKAGE_NAME, requireSuccess = false)
     }
 
+    @Before
+    fun setUpTest() {
+        SystemUtil.runWithShellPermissionIdentity {
+            was7DayToggleEnabled = DeviceConfig.getBoolean(NAMESPACE_PRIVACY,
+                    PRIV_DASH_7_DAY_ENABLED, false)
+            DeviceConfig.setProperty(NAMESPACE_PRIVACY,
+                    PRIV_DASH_7_DAY_ENABLED, true.toString(), false)
+        }
+    }
+
+    @After
+    fun tearDownTest() {
+        SystemUtil.runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(NAMESPACE_PRIVACY,
+                    PRIV_DASH_7_DAY_ENABLED, was7DayToggleEnabled.toString(), false)
+        }
+    }
+
     @Test
     fun testMicrophoneAccessShowsUpOnPrivacyDashboard() {
         openMicrophoneApp(INTENT_ACTION_1)
@@ -100,6 +126,56 @@
     }
 
     @Test
+    fun testToggleFrom24HoursTo7Days() {
+        openMicrophoneApp(INTENT_ACTION_1)
+        waitFindObject(By.textContains(APP_LABEL_1))
+
+        openPermissionDashboard()
+        waitFindObject(By.descContains(MORE_OPTIONS)).click()
+        try {
+            waitFindObject(By.text(SHOW_7_DAYS)).click()
+        } catch (exception: RuntimeException) {
+            // If privacy dashboard was set to 7d instead of 24h,
+            // it will not be able to find the "Show 7 days" option.
+            // This block is to toggle it back to 24h if that happens.
+            waitFindObject(By.text(SHOW_24_HOURS)).click()
+            waitFindObject(By.descContains(MORE_OPTIONS)).click()
+            waitFindObject(By.text(SHOW_7_DAYS)).click()
+        }
+
+        waitFindObject(By.res("android:id/title").textContains("Microphone"))
+        waitFindObject(By.textContains(DASHBOARD_7_DAYS_DESCRIPTION))
+
+        pressBack()
+    }
+
+    @Test
+    fun testToggleFrom24HoursTo7DaysInTimeline() {
+        openMicrophoneApp(INTENT_ACTION_1)
+        waitFindObject(By.textContains(APP_LABEL_1))
+
+        openMicrophoneTimeline()
+        waitFindObject(By.descContains(MORE_OPTIONS)).click()
+        try {
+            waitFindObject(By.text(SHOW_7_DAYS)).click()
+        } catch (exception: RuntimeException) {
+            // If privacy dashboard was set to 7d instead of 24h,
+            // it will not be able to find the "Show 7 days" option.
+            // This block is to toggle it back to 24h if that happens.
+            waitFindObject(By.text(SHOW_24_HOURS)).click()
+            waitFindObject(By.descContains(MORE_OPTIONS)).click()
+            waitFindObject(By.text(SHOW_7_DAYS)).click()
+        }
+
+        waitFindObject(By.descContains(micLabel))
+        waitFindObject(By.textContains(APP_LABEL_1))
+        waitFindObject(By.textContains(TIMELINE_7_DAYS_DESCRIPTION))
+
+        pressBack()
+    }
+
+    @Test
+    @Ignore
     fun testMicrophoneTimelineWithOneApp() {
         openMicrophoneApp(INTENT_ACTION_1)
         waitFindObject(By.textContains(APP_LABEL_1))
@@ -135,15 +211,6 @@
         })
     }
 
-    private fun openMicrophoneTimeline() {
-        SystemUtil.runWithShellPermissionIdentity {
-            context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
-                putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.MICROPHONE)
-                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            })
-        }
-    }
-
     private fun openPermissionDashboard() {
         SystemUtil.runWithShellPermissionIdentity {
             context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).apply {
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt
new file mode 100644
index 0000000..fcd9036
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTapjackingTest.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission3.cts
+
+import android.content.ComponentName
+import android.content.Intent
+import android.support.test.uiautomator.By
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.lang.Exception
+
+/**
+ * Tests permission review screen can't be tapjacked
+ */
+class PermissionReviewTapjackingTest : BaseUsePermissionTest() {
+
+    companion object {
+        const val HELPER_APP_OVERLAY = "$APK_DIRECTORY/CtsHelperAppOverlay.apk"
+        private const val HELPER_PACKAGE_NAME = "android.permission3.cts.helper.overlay"
+    }
+
+    @Before
+    fun installApp22AndApprovePermissionReview() {
+        assumeFalse(packageManager.arePermissionsIndividuallyControlled())
+
+        installPackage(APP_APK_PATH_22)
+        installPackage(HELPER_APP_OVERLAY)
+
+        SystemUtil.runShellCommandOrThrow(
+                "appops set $HELPER_PACKAGE_NAME android:system_alert_window allow")
+    }
+
+    @After
+    fun uninstallPackages() {
+        SystemUtil.runShellCommandOrThrow(
+                "pm uninstall $APP_PACKAGE_NAME")
+        SystemUtil.runShellCommandOrThrow(
+                "pm uninstall $HELPER_PACKAGE_NAME")
+    }
+
+    @Test
+    fun testOverlaysAreHidden() {
+        context.startActivity(Intent()
+                .setComponent(ComponentName(HELPER_PACKAGE_NAME,
+                        "$HELPER_PACKAGE_NAME.OverlayActivity"))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+        findOverlay()
+
+        context.startActivity(Intent()
+                .setComponent(ComponentName(APP_PACKAGE_NAME,
+                        "$APP_PACKAGE_NAME.FinishOnCreateActivity"))
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        )
+
+        waitFindObject(By.res("com.android.permissioncontroller:id/permissions_message"))
+
+        try {
+            findOverlay()
+            Assert.fail("Overlay was displayed")
+        } catch (e: Exception) {
+            // expected
+        }
+
+        pressHome()
+        findOverlay()
+    }
+
+    private fun findOverlay() = waitFindObject(By.text("Find me!"))
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
index 6b8678d..6e0f72c 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionReviewTest.kt
@@ -20,11 +20,13 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.pm.PackageManager
+import android.os.Build
 import android.os.Bundle
 import android.os.Handler
 import android.os.Looper
 import android.os.ResultReceiver
 import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
 import androidx.test.runner.AndroidJUnit4
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
@@ -114,6 +116,15 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "TIRAMISU")
+    fun testNotificationPermissionAddedToReview() {
+        startAppActivityAndAssertResultCode(Activity.RESULT_CANCELED) {
+            waitFindObject(By.text("Notifications"), 5000L)
+            clickPermissionReviewCancel()
+        }
+    }
+
+    @Test
     fun testReviewPermissionWhenServiceIsBound() {
         val results = LinkedBlockingQueue<Int>()
         // We are starting a activity instead of the service directly, because
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
index 22074f7..9550934 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionSplitTest.kt
@@ -16,6 +16,8 @@
 
 package android.permission3.cts
 
+import android.os.Build
+import androidx.test.filters.SdkSuppress
 import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Test
@@ -53,6 +55,27 @@
         testLocationPermissionSplit(false)
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    fun testBodySensorSplit() {
+        installPackage(APP_APK_PATH_31)
+        testBodySensorPermissionSplit(true)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    fun testBodySensorSplit32() {
+        installPackage(APP_APK_PATH_32)
+        testBodySensorPermissionSplit(true)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    fun testBodySensorNonSplit() {
+        installPackage(APP_APK_PATH_LATEST)
+        testBodySensorPermissionSplit(false)
+    }
+
     private fun testLocationPermissionSplit(expectSplit: Boolean) {
         assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, false)
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, false)
@@ -69,4 +92,21 @@
 
         assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, expectSplit)
     }
+
+    private fun testBodySensorPermissionSplit(expectSplit: Boolean) {
+        assertAppHasPermission(android.Manifest.permission.BODY_SENSORS, false)
+        assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, false)
+
+        requestAppPermissionsAndAssertResult(
+                android.Manifest.permission.BODY_SENSORS to true
+        ) {
+            if (expectSplit) {
+                clickPermissionRequestSettingsLinkAndAllowAlways()
+            } else {
+                clickPermissionRequestAllowForegroundButton()
+            }
+        }
+
+        assertAppHasPermission(android.Manifest.permission.BODY_SENSORS_BACKGROUND, expectSplit)
+    }
 }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
index 905d46e..b5b22cf 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
@@ -20,7 +20,9 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.graphics.Point
+import android.os.Build
 import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.SystemUtil
 import org.junit.Assume.assumeFalse
 import org.junit.Before
@@ -55,6 +57,7 @@
         tryClicking(buttonCenter)
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     @Test
     fun testTapjackGrantDialog_partialOverlay() {
         // PermissionController for television uses a floating window.
@@ -75,10 +78,10 @@
         // Wait for overlay to hide the dialog
         context.sendBroadcast(Intent(ACTION_SHOW_OVERLAY)
                 .putExtra(EXTRA_FULL_OVERLAY, false)
-                .putExtra(DIALOG_LEFT, overlayButtonBounds.left)
-                .putExtra(DIALOG_TOP, overlayButtonBounds.top)
-                .putExtra(DIALOG_RIGHT, overlayButtonBounds.right)
-                .putExtra(MESSAGE_BOTTOM, overlayButtonBounds.bottom))
+                .putExtra(OVERLAY_LEFT, overlayButtonBounds.left)
+                .putExtra(OVERLAY_TOP, overlayButtonBounds.top)
+                .putExtra(OVERLAY_RIGHT, overlayButtonBounds.right)
+                .putExtra(OVERLAY_BOTTOM, overlayButtonBounds.bottom))
         waitFindObject(By.res("android.permission3.cts.usepermission:id/overlay"))
 
         tryClicking(foregroundButtonCenter)
@@ -101,19 +104,16 @@
         // Permission should not be granted
         assertAppHasPermission(ACCESS_FINE_LOCATION, false)
 
-        // On Automotive the dialog gets closed by the tapjacking activity popping up
-        if (!isAutomotive) {
-            // Verify that clicking the dialog without the overlay still works
-            context.sendBroadcast(Intent(ACTION_HIDE_OVERLAY))
-            SystemUtil.eventually({
-                if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
-                        PackageManager.PERMISSION_DENIED) {
-                    uiDevice.click(buttonCenter.x, buttonCenter.y)
-                    Thread.sleep(100)
-                }
-                assertAppHasPermission(ACCESS_FINE_LOCATION, true)
-            }, 10000)
-        }
+        // Verify that clicking the dialog without the overlay still works
+        context.sendBroadcast(Intent(ACTION_HIDE_OVERLAY))
+        SystemUtil.eventually({
+            if (packageManager.checkPermission(ACCESS_FINE_LOCATION, APP_PACKAGE_NAME) ==
+                    PackageManager.PERMISSION_DENIED) {
+                uiDevice.click(buttonCenter.x, buttonCenter.y)
+                Thread.sleep(100)
+            }
+            assertAppHasPermission(ACCESS_FINE_LOCATION, true)
+        }, 10000)
     }
 
     companion object {
@@ -122,9 +122,9 @@
 
         const val EXTRA_FULL_OVERLAY = "android.permission3.cts.usepermission.extra.FULL_OVERLAY"
 
-        const val DIALOG_LEFT = "android.permission3.cts.usepermission.extra.DIALOG_LEFT"
-        const val DIALOG_TOP = "android.permission3.cts.usepermission.extra.DIALOG_TOP"
-        const val DIALOG_RIGHT = "android.permission3.cts.usepermission.extra.DIALOG_RIGHT"
-        const val MESSAGE_BOTTOM = "android.permission3.cts.usepermission.extra.MESSAGE_BOTTOM"
+        const val OVERLAY_LEFT = "android.permission3.cts.usepermission.extra.OVERLAY_LEFT"
+        const val OVERLAY_TOP = "android.permission3.cts.usepermission.extra.OVERLAY_TOP"
+        const val OVERLAY_RIGHT = "android.permission3.cts.usepermission.extra.OVERLAY_RIGHT"
+        const val OVERLAY_BOTTOM = "android.permission3.cts.usepermission.extra.OVERLAY_BOTTOM"
     }
 }
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index 26fe615..f9c4496 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -16,8 +16,10 @@
 
 package android.permission3.cts
 
+import android.content.pm.PackageManager
 import androidx.test.filters.FlakyTest
-import com.android.modules.utils.build.SdkLevel
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -182,11 +184,7 @@
         // Request the permission and allow it
         // Make sure the permission is granted
         requestAppPermissionsAndAssertResult(android.Manifest.permission.CAMERA to true) {
-            if (SdkLevel.isAtLeastS()) {
-                clickPermissionRequestAllowForegroundButton()
-            } else {
-                clickPermissionRequestAllowButton()
-            }
+            clickPermissionRequestAllowForegroundButton()
         }
     }
 
@@ -248,11 +246,10 @@
             android.Manifest.permission.READ_SMS,
             android.Manifest.permission.CALL_PHONE,
             android.Manifest.permission.RECORD_AUDIO,
-            android.Manifest.permission.BODY_SENSORS,
             android.Manifest.permission.CAMERA,
             android.Manifest.permission.READ_EXTERNAL_STORAGE, targetSdk = 23
         )
-        // Don't use UI for granting location permission as this shows another dialog
+        // Don't use UI for granting location and sensor permissions as they show another dialog
         uiAutomation.grantRuntimePermission(
             APP_PACKAGE_NAME, android.Manifest.permission.ACCESS_FINE_LOCATION
         )
@@ -262,6 +259,9 @@
         uiAutomation.grantRuntimePermission(
             APP_PACKAGE_NAME, android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
         )
+        uiAutomation.grantRuntimePermission(
+            APP_PACKAGE_NAME, android.Manifest.permission.BODY_SENSORS
+        )
 
         uninstallPackage(APP_PACKAGE_NAME)
         installPackage(APP_APK_PATH_23)
@@ -290,11 +290,7 @@
             null to false,
             android.Manifest.permission.RECORD_AUDIO to true
         ) {
-            if (SdkLevel.isAtLeastS()) {
-                clickPermissionRequestAllowForegroundButton()
-            } else {
-                clickPermissionRequestAllowButton()
-            }
+            clickPermissionRequestAllowForegroundButton()
             clickPermissionRequestAllowButton()
         }
     }
@@ -306,6 +302,34 @@
         requestAppPermissionsAndAssertResult(INVALID_PERMISSION to false) {}
     }
 
+    @Test
+    fun testAskButtonSetsFlags() {
+        Assume.assumeFalse("other form factors might not support the ask button",
+                isTv || isAutomotive || isWatch)
+
+        grantAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, true)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, true)
+
+        revokeAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+        SystemUtil.runWithShellPermissionIdentity {
+            val perms = listOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+                    android.Manifest.permission.ACCESS_FINE_LOCATION,
+                    android.Manifest.permission.ACCESS_COARSE_LOCATION)
+            for (perm in perms) {
+                var flags = packageManager.getPermissionFlags(perm, APP_PACKAGE_NAME, context.user)
+                Assert.assertEquals("USER_SET should not be set for $perm", 0,
+                        flags and PackageManager.FLAG_PERMISSION_USER_SET)
+                Assert.assertEquals("USER_FIXED should not be set for $perm", 0,
+                        flags and PackageManager.FLAG_PERMISSION_USER_FIXED)
+                Assert.assertEquals("ONE_TIME should be set for $perm",
+                        PackageManager.FLAG_PERMISSION_ONE_TIME,
+                        flags and PackageManager.FLAG_PERMISSION_ONE_TIME)
+            }
+        }
+    }
+
     private fun denyPermissionRequestWithPrejudice() {
         if (isTv || isWatch) {
             clickPermissionRequestDontAskAgainButton()
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
index 3cef547..c3b2bb5 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest30WithBluetooth.kt
@@ -26,8 +26,10 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT
+import android.location.LocationManager
 import android.os.Build
 import android.os.Process
+import android.os.UserHandle
 import androidx.test.InstrumentationRegistry
 import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.SystemUtil.runShellCommandOrThrow
@@ -53,6 +55,8 @@
         "android.permission3.cts.usepermission"
     private lateinit var bluetoothAdapter: BluetoothAdapter
     private var bluetoothAdapterWasEnabled: Boolean = false
+    private val locationManager = context.getSystemService(LocationManager::class.java)!!
+    private var locationWasEnabled: Boolean = false
 
     private enum class BluetoothScanResult {
         UNKNOWN, ERROR, EXCEPTION, EMPTY, FILTERED, FULL
@@ -78,6 +82,28 @@
         enableTestMode()
     }
 
+    @Before
+    fun enableLocation() {
+        val userHandle: UserHandle = Process.myUserHandle()
+        locationWasEnabled = locationManager.isLocationEnabledForUser(userHandle)
+        if (!locationWasEnabled) {
+            runWithShellPermissionIdentity {
+                locationManager.setLocationEnabledForUser(true, userHandle)
+            }
+        }
+    }
+
+    @After
+    fun disableLocation() {
+        val userHandle: UserHandle = Process.myUserHandle()
+
+        if (!locationWasEnabled) {
+            runWithShellPermissionIdentity {
+                locationManager.setLocationEnabledForUser(false, userHandle)
+            }
+        }
+    }
+
     @After
     fun disableBluetooth() {
         assumeTrue(supportsBluetooth())
@@ -91,6 +117,9 @@
 
     @Test
     fun testGivenBluetoothIsDeniedWhenScanIsAttemptedThenThenGetEmptyScanResult() {
+        assertTrue("Please enable location to run this test. Bluetooth scanning " +
+                "requires location to be enabled.", locationManager.isLocationEnabled())
+
         assertBluetoothRevokedCompatState(revoked = false)
         // Should return empty while the app does not have location
         assertEquals(BluetoothScanResult.EMPTY, scanForBluetoothDevices())
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
new file mode 100644
index 0000000..d6e7a69
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTestLatest.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.permission3.cts
+
+import android.os.Build
+import androidx.test.filters.SdkSuppress
+import org.junit.Assert
+import org.junit.Test
+
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+class PermissionTestLatest : BaseUsePermissionTest() {
+    /**
+     * Not exactly a cts type test but it needs to be run continuously. This test is supposed to
+     * start failing once the sdk integer is decided for T. This is important to have because it was
+     * assumed that the sdk int will be 33 in frameworks/base/data/etc/platform.xml. This test
+     * should be removed once the sdk is finalized.
+     */
+    @Test
+    fun testTApiVersionCodeIsNotSet() {
+        Assert.assertEquals(Build.VERSION_CODES.S_V2, Build.VERSION.SDK_INT)
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
new file mode 100644
index 0000000..9dc0b27
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,152 @@
+/*
+ * 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.permission3.cts
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.provider.Settings
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Banner card display tests on sensors being blocked
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class SensorBlockedBannerTest : BaseUsePermissionTest() {
+    companion object {
+        const val LOCATION = -1
+        const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
+    }
+
+    val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+    val locationManager = context.getSystemService(LocationManager::class.java)!!
+    lateinit var originalEnabledValue: String
+
+    private val permToLabel = mapOf(CAMERA to "privdash_label_camera",
+            MICROPHONE to "privdash_label_microphone",
+            LOCATION to "privdash_label_location")
+
+    private val permToTitle = mapOf(CAMERA to "blocked_camera_title",
+            MICROPHONE to "blocked_microphone_title",
+            LOCATION to "blocked_location_title")
+
+    @Before
+    fun setup() {
+        Assume.assumeFalse(isTv)
+        installPackage(APP_APK_PATH_31)
+        runWithShellPermissionIdentity {
+            originalEnabledValue = DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+                WARNING_BANNER_ENABLED, false.toString())
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                WARNING_BANNER_ENABLED, true.toString(), false)
+        }
+    }
+
+    @After
+    fun restoreWarningBannerState() {
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    WARNING_BANNER_ENABLED, originalEnabledValue, false)
+        }
+    }
+
+    private fun navigateAndTest(sensor: Int) {
+        val permLabel = permToLabel.getOrDefault(sensor, "Break")
+        val intent = Intent(Settings.ACTION_PRIVACY_SETTINGS)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        context.startActivity(intent)
+        click(By.text(getPermissionControllerString("app_permission_manager")))
+        click(By.text(getPermissionControllerString(permLabel)))
+        val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+        waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+        pressBack()
+        pressBack()
+        pressBack()
+    }
+
+    private fun runSensorTest(sensor: Int) {
+        val blocked = isSensorPrivacyEnabled(sensor)
+        if (!blocked) {
+            setSensor(sensor, true)
+        }
+        navigateAndTest(sensor)
+        if (!blocked) {
+            setSensor(sensor, false)
+        }
+    }
+
+    @Test
+    fun testCameraCardDisplayed() {
+        Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+        runSensorTest(CAMERA)
+    }
+
+    @Test
+    fun testMicCardDisplayed() {
+        Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE))
+        runSensorTest(MICROPHONE)
+    }
+
+    @Test
+    fun testLocationCardDisplayed() {
+        runSensorTest(LOCATION)
+    }
+
+    private fun setSensor(sensor: Int, enable: Boolean) {
+        if (sensor == LOCATION) {
+            runWithShellPermissionIdentity {
+                locationManager.setLocationEnabledForUser(!enable,
+                    android.os.Process.myUserHandle())
+                if (enable) {
+                    try {
+                        waitFindObjectOrNull(By.text("CLOSE"))?.click()
+                    } catch (e: Exception) {
+                        // Do nothing, warning didn't show up so test can proceed
+                    }
+                }
+            }
+        } else {
+            runWithShellPermissionIdentity {
+                sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER,
+                    sensor, enable)
+            }
+        }
+    }
+
+    private fun isSensorPrivacyEnabled(sensor: Int): Boolean {
+        return if (sensor == LOCATION) {
+            callWithShellPermissionIdentity {
+                !locationManager.isLocationEnabled()
+            }
+        } else {
+            callWithShellPermissionIdentity {
+                sensorPrivacyManager.isSensorPrivacyEnabled(sensor)
+            }
+        }
+    }
+}
diff --git a/tests/tests/permission4/AndroidTest.xml b/tests/tests/permission4/AndroidTest.xml
index 71353aa..78fe62f 100644
--- a/tests/tests/permission4/AndroidTest.xml
+++ b/tests/tests/permission4/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="test-suite-tag" value="cts" />
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
index e8f503e..4128de9 100644
--- a/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
+++ b/tests/tests/permission4/src/android/permission4/cts/CameraMicIndicatorsPermissionTest.kt
@@ -176,8 +176,10 @@
         } else if (packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
             assertCarIndicatorsShown(useMic, useCamera, useHotword)
         } else {
+            // Hotword gets remapped to RECORD_AUDIO on handheld, so handheld should show a mic
+            // indicator
             uiDevice.openQuickSettings()
-            assertPrivacyChipAndIndicatorsPresent(useMic, useCamera)
+            assertPrivacyChipAndIndicatorsPresent(useMic || useHotword, useCamera)
         }
     }
 
@@ -201,7 +203,18 @@
 
     private fun assertCarIndicatorsShown(useMic: Boolean, useCamera: Boolean, useHotword: Boolean) {
         // Ensure the privacy chip is present (or not)
-        val chipFound = isChipPresent()
+        var chipFound = false
+        try {
+            eventually {
+                val privacyChip = uiDevice.findObject(By.res(PRIVACY_CHIP_ID))
+                assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
+                privacyChip.click()
+                chipFound = true
+            }
+        } catch (e: Exception) {
+            // Handle more gracefully below
+        }
+
         if (useMic) {
             assertTrue("Did not find chip", chipFound)
         } else if (useHotword || useCamera) {
@@ -227,13 +240,11 @@
     }
 
     private fun assertPrivacyChipAndIndicatorsPresent(useMic: Boolean, useCamera: Boolean) {
-        // Ensure the privacy chip is present (or not)
-        val chipFound = isChipPresent()
-        if (useMic || useCamera) {
-            assertTrue("Did not find chip", chipFound)
-        } else { // hotword
-            assertFalse("Found chip, but did not expect to", chipFound)
-            return
+        // Ensure the privacy chip is present
+        eventually {
+            val privacyChip = uiDevice.findObject(UiSelector().resourceId(PRIVACY_CHIP_ID))
+            assertTrue("view with id $PRIVACY_CHIP_ID not found", privacyChip.exists())
+            privacyChip.click()
         }
 
         eventually {
@@ -250,21 +261,6 @@
         }
     }
 
-    private fun isChipPresent(): Boolean {
-        var chipFound = false
-        try {
-            eventually {
-                val privacyChip = uiDevice.findObject(By.res(PRIVACY_CHIP_ID))
-                assertNotNull("view with id $PRIVACY_CHIP_ID not found", privacyChip)
-                privacyChip.click()
-                chipFound = true
-            }
-        } catch (e: Exception) {
-            // Handle more gracefully after
-        }
-        return chipFound
-    }
-
     private fun pressBack() {
         uiDevice.pressBack()
         waitForIdle()
diff --git a/tests/tests/permission5/AndroidTest.xml b/tests/tests/permission5/AndroidTest.xml
index d449d9f..953df0d 100644
--- a/tests/tests/permission5/AndroidTest.xml
+++ b/tests/tests/permission5/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="test-suite-tag" value="cts" />
 
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
diff --git a/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt b/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt
new file mode 100644
index 0000000..b7762c6
--- /dev/null
+++ b/tests/tests/permission5/src/android/permission5/cts/PermissionCheckerTest.kt
@@ -0,0 +1,158 @@
+/*
+ * 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.permission5.cts
+
+import android.app.AppOpsManager
+import android.content.AttributionSource
+import android.os.Process
+import android.permission.PermissionManager
+import android.platform.test.annotations.AppModeFull
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+
+@AppModeFull(reason = "Instant apps cannot hold READ_CALENDAR")
+class PermissionCheckerTest {
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context = instrumentation.getContext()
+    private val packageManager = context.packageManager
+    private val appOpsManager = context.getSystemService(AppOpsManager::class.java)
+    private val permissionManager = context.getSystemService(PermissionManager::class.java)
+    private val currentUser = Process.myUserHandle()
+
+    private val helperUid = packageManager.getPackageUid(HELPER_PACKAGE_NAME, 0)
+    private val helperAttributionSource = AttributionSource.Builder(helperUid)
+        .setPackageName(HELPER_PACKAGE_NAME)
+        .build()
+
+    @Before
+    @After
+    fun resetHelperPermissionState() {
+        runWithShellPermissionIdentity {
+            Thread.sleep(1000)
+            packageManager.grantRuntimePermission(
+                HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+            )
+            Thread.sleep(1000)
+            appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_ALLOWED)
+            Thread.sleep(1000)
+        }
+    }
+
+    @Test
+    fun testCheckPermissionForPreflight() {
+        assertThat(
+            permissionManager.checkPermissionForPreflight(
+                HELPER_PERMISSION_NAME, helperAttributionSource
+            )
+        ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+
+        runWithShellPermissionIdentity {
+            appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+        }
+        assertThat(
+            permissionManager.checkPermissionForPreflight(
+                HELPER_PERMISSION_NAME, helperAttributionSource
+            )
+        ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+
+        runWithShellPermissionIdentity {
+            packageManager.revokeRuntimePermission(
+                HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+            )
+        }
+        assertThat(
+            permissionManager.checkPermissionForPreflight(
+                HELPER_PERMISSION_NAME, helperAttributionSource
+            )
+        ).isEqualTo(PermissionManager.PERMISSION_HARD_DENIED)
+    }
+
+    @Test
+    fun testCheckPermissionForDataDelivery() {
+        // checkPermissionForDataDelivery() requires UPDATE_APP_OPS_STATS.
+        runWithShellPermissionIdentity {
+            assertThat(
+                permissionManager.checkPermissionForDataDelivery(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+
+            appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+            assertThat(
+                permissionManager.checkPermissionForDataDelivery(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+
+            packageManager.revokeRuntimePermission(
+                HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+            )
+            assertThat(
+                permissionManager.checkPermissionForDataDelivery(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_HARD_DENIED)
+        }
+    }
+
+    @Test
+    fun testCheckPermissionForDataDeliveryFromDataSource() {
+        runWithShellPermissionIdentity({
+            assertThat(
+                permissionManager.checkPermissionForDataDeliveryFromDataSource(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_GRANTED)
+        }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+
+        runWithShellPermissionIdentity {
+            appOpsManager.setUidMode(HELPER_APP_OP_NAME, helperUid, AppOpsManager.MODE_IGNORED)
+        }
+
+        runWithShellPermissionIdentity({
+            assertThat(
+                permissionManager.checkPermissionForDataDeliveryFromDataSource(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+        }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+
+        runWithShellPermissionIdentity {
+            packageManager.revokeRuntimePermission(
+                HELPER_PACKAGE_NAME, HELPER_PERMISSION_NAME, currentUser
+            )
+        }
+
+        runWithShellPermissionIdentity({
+            assertThat(
+                permissionManager.checkPermissionForDataDeliveryFromDataSource(
+                    HELPER_PERMISSION_NAME, helperAttributionSource, null
+                )
+            ).isEqualTo(PermissionManager.PERMISSION_SOFT_DENIED)
+        }, android.Manifest.permission.UPDATE_APP_OPS_STATS)
+    }
+
+    companion object {
+        private const val HELPER_PACKAGE_NAME = "android.permission5.cts.blamed"
+        private const val HELPER_PERMISSION_NAME = android.Manifest.permission.READ_CALENDAR
+        private const val HELPER_APP_OP_NAME = AppOpsManager.OPSTR_READ_CALENDAR
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
index 97dfd37..71ffe0d 100644
--- a/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
+++ b/tests/tests/provider/src/android/provider/cts/SettingsPanelTest.java
@@ -99,6 +99,7 @@
 
     @Test
     public void internetDialog_correctPackage() {
+        assumeTrue(mHasTouchScreen);
         launchInternetDialog();
 
         String currentPackage = mDevice.getCurrentPackageName();
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
index ff5a1c8..c0e100c 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -24,6 +24,7 @@
 import android.provider.MediaStore.Audio.Media;
 import android.provider.cts.ProviderTestUtils;
 
+import androidx.annotation.RequiresApi;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -91,7 +92,6 @@
         public static final int IS_RINGTONE = 0;
         public static final int IS_NOTIFICATION = 0;
         public static final int IS_ALARM = 0;
-        public static final int IS_RECORDING = 0;
         public static final int IS_MUSIC = 1;
         public static final int YEAR = 1992;
         public static final int TRACK = 1;
@@ -135,7 +135,6 @@
             values.put(Media.IS_MUSIC, IS_MUSIC);
             values.put(Media.IS_ALARM, IS_ALARM);
             values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
-            values.put(Media.IS_RECORDING, IS_RECORDING);
             values.put(Media.IS_RINGTONE, IS_RINGTONE);
             return values;
         }
@@ -278,6 +277,28 @@
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.S)
+    public static class Audio7 extends Audio1 {
+        public static final int IS_RECORDING = 0;
+
+        private Audio7() {
+        }
+
+        private static Audio7 sInstance = new Audio7();
+
+        public static Audio7 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + ".recording.mp3");
+            values.put(Media.IS_RECORDING, IS_RECORDING);
+            return values;
+        }
+    }
+
     @Test
     public void testStub() {
         // No-op test here to keep atest happy
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
index d5a4e4a..c6768ab 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -221,12 +221,9 @@
                 Optional.of("Android/media/android.provider.cts/foo"), null));
         assertFalse(updatePlacement(uri,
                 Optional.of("Android/media/com.example/foo"), null));
-        try {
-            assertTrue(updatePlacementOrThrow(uri, Optional.of("DCIM"), null));
-        } catch (IllegalArgumentException tolerated) {
-            // update() above can either succeed or throw IllegalArgumentExxception based on the
-            // MediaProvider version.
-        }
+        assertTrue(updatePlacement(uri,
+                Optional.of("DCIM"), null));
+        assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
     }
 
     @Test
@@ -236,17 +233,13 @@
         final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
                 mExternalImages, "image/jpeg");
 
+        assertTrue(updatePlacement(uri,
+                Optional.of("Android/media/android.provider.cts/foo"), null));
         assertFalse(updatePlacement(uri,
                 Optional.of("Android/media/com.example/foo"), null));
         assertTrue(updatePlacement(uri,
                 Optional.of("DCIM"), null));
-        try {
-            assertTrue(updatePlacementOrThrow(uri,
-                    Optional.of("Android/media/android.provider.cts/foo"), null));
-        } catch (IllegalArgumentException tolerated) {
-            // update() above can either succeed or throw IllegalArgumentExxception based on the
-            // MediaProvider version.
-        }
+        assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
     }
 
     @Test
@@ -327,8 +320,8 @@
                 ProviderTestUtils.stageFile(R.raw.scenery, file));
     }
 
-    private boolean updatePlacementOrThrow(Uri uri, Optional<String> path,
-            Optional<String> displayName) throws Exception {
+    private boolean updatePlacement(Uri uri, Optional<String> path, Optional<String> displayName)
+            throws Exception {
         final ContentValues values = new ContentValues();
         if (path != null) {
             values.put(MediaColumns.RELATIVE_PATH, path.orElse(null));
@@ -336,13 +329,8 @@
         if (displayName != null) {
             values.put(MediaColumns.DISPLAY_NAME, displayName.orElse(null));
         }
-        return (mContentResolver.update(uri, values, null, null) == 1);
-    }
-
-    private boolean updatePlacement(Uri uri, Optional<String> path,
-            Optional<String> displayName) {
         try {
-            return updatePlacementOrThrow(uri, path, displayName);
+            return (mContentResolver.update(uri, values, null, null) == 1);
         } catch (Exception tolerated) {
             return false;
         }
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index a196c5a..6a4c199 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -36,10 +36,10 @@
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Audio.Media;
 import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
 import android.provider.cts.ProviderTestUtils;
 import android.provider.cts.R;
 import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio7;
 import android.provider.cts.media.MediaStoreUtils.PendingParams;
 import android.provider.cts.media.MediaStoreUtils.PendingSession;
 import android.util.Log;
@@ -157,7 +157,6 @@
             assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
             assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
             assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
-            assertEquals(Audio1.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
             assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
             assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
             String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
@@ -186,6 +185,41 @@
         }
     }
 
+    /**
+     * The test case checks below behaviors:
+     * 1. The IS_RECORDING column is in Audio table in MediaStore's database since S OS. Insert with
+     *    IS_RECORDING column doesn't fail and we can query the result with IS_RECORDING column.
+     * 2. Validate IS_RECORDING is correctly set for database row.
+     */
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    public void testStoreNotAudioRecordingMedia() {
+        Audio7 audio7 = Audio7.getInstance();
+        ContentValues values = audio7.getContentValues(mVolumeName);
+        //insert
+        Uri mediaUri = Media.getContentUri(mVolumeName);
+        Uri uri = mContentResolver.insert(mediaUri, values);
+        assertNotNull(uri);
+
+        try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id > 0);
+            String expected = audio7.getContentValues(mVolumeName).getAsString(Media.DATA);
+            assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+            assertEquals(Audio7.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+            assertEquals(Audio7.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+            assertEquals(Audio7.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+            assertEquals(Audio7.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+            assertEquals(Audio7.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+            assertEquals(Audio7.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
+        } finally {
+            // delete
+            mContentResolver.delete(uri, null, null);
+        }
+    }
+
     @Test(timeout = 60000)
     public void testCanonicalize() throws Exception {
         // Remove all audio left over from other tests
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
index fd84677..693284f 100644
--- a/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SecureTest.java
@@ -114,10 +114,21 @@
         } catch (SettingNotFoundException expected) {
         }
 
+        if (!isFloat(Secure.getString(cr, STRING_VALUE_SETTING))) {
+            try {
+                Secure.getFloat(cr, STRING_VALUE_SETTING);
+                fail("SettingNotFoundException should have been thrown!");
+            } catch (SettingNotFoundException expected) {
+            }
+        }
+    }
+
+    private boolean isFloat(String str) {
         try {
-            Secure.getFloat(cr, STRING_VALUE_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
+            Float.parseFloat(str);
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
         }
     }
 
@@ -137,10 +148,21 @@
         } catch (SettingNotFoundException expected) {
         }
 
+        if (!isLong(Secure.getString(cr, STRING_VALUE_SETTING))) {
+            try {
+                Secure.getLong(cr, STRING_VALUE_SETTING);
+                fail("SettingNotFoundException should have been thrown!");
+            } catch (SettingNotFoundException expected) {
+            }
+        }
+    }
+
+    private boolean isLong(String str) {
         try {
-            Secure.getLong(cr, STRING_VALUE_SETTING);
-            fail("SettingNotFoundException should have been thrown!");
-        } catch (SettingNotFoundException expected) {
+            Long.parseLong(str);
+            return true;
+        } catch (NumberFormatException e) {
+            return false;
         }
     }
 
diff --git a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
index 2fc25b8..2f5155a 100644
--- a/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
+++ b/tests/tests/provider/src/android/provider/cts/settings/Settings_SystemTest.java
@@ -147,29 +147,35 @@
     @AsbSecurityTest(cveBugId = 156260178)
     public void testSystemSettingsRejectInvalidFontSizeScale() throws SettingNotFoundException {
         final ContentResolver cr = InstrumentationRegistry.getTargetContext().getContentResolver();
-        // First put in a valid value
-        assertTrue(System.putFloat(cr, FLOAT_FIELD, 1.15f));
+        final String originalFloatValue = System.getString(cr, FLOAT_FIELD);
         try {
-            assertFalse(System.putFloat(cr, FLOAT_FIELD, Float.MAX_VALUE));
-            fail("Should throw");
-        } catch (IllegalArgumentException e) {
+            // First put in a valid value
+            assertTrue(System.putFloat(cr, FLOAT_FIELD, 1.15f));
+            assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+            try {
+                assertFalse(System.putFloat(cr, FLOAT_FIELD, Float.MAX_VALUE));
+                fail("Should throw");
+            } catch (IllegalArgumentException e) {
+            }
+            try {
+                assertFalse(System.putFloat(cr, FLOAT_FIELD, -1f));
+                fail("Should throw");
+            } catch (IllegalArgumentException e) {
+            }
+            try {
+                assertFalse(System.putFloat(cr, FLOAT_FIELD, 0.1f));
+                fail("Should throw");
+            } catch (IllegalArgumentException e) {
+            }
+            try {
+                assertFalse(System.putFloat(cr, FLOAT_FIELD, 30.0f));
+                fail("Should throw");
+            } catch (IllegalArgumentException e) {
+            }
+            assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
+        } finally {
+            assertTrue(System.putString(cr, FLOAT_FIELD, originalFloatValue));
         }
-        try {
-            assertFalse(System.putFloat(cr, FLOAT_FIELD, -1f));
-            fail("Should throw");
-        } catch (IllegalArgumentException e) {
-        }
-        try {
-            assertFalse(System.putFloat(cr, FLOAT_FIELD, 0.1f));
-            fail("Should throw");
-        } catch (IllegalArgumentException e) {
-        }
-        try {
-            assertFalse(System.putFloat(cr, FLOAT_FIELD, 30.0f));
-            fail("Should throw");
-        } catch (IllegalArgumentException e) {
-        }
-        assertEquals(1.15f, System.getFloat(cr, FLOAT_FIELD), 0.001);
     }
 
     @Test
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
index c069760..9ee194e 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/ImageProcessingTest.java
@@ -119,9 +119,8 @@
         byte[] srcData = new byte[w * h * 4];
         byte[] dstData = new byte[w * h * 4];
         byte[] resultData = new byte[w * h * 4];
-        Script.LaunchOptions opt = new Script.LaunchOptions();
-        // unclipped but with options
-        for (int i = 0; i < 28; i++) {
+
+        for (int i = 0; i < 14; i++) {
             buildSrc(srcData, w, h);
             buildDst(dstData, w, h);
             src.copyFromUnchecked(srcData);
@@ -170,51 +169,71 @@
                 case 13:
                     mBlend.forEachMultiply(src, dst);
                     break;
-                case 14:
+            }
+            dst.copyTo(resultData);
+            String name = javaBlend(i, srcData, dstData, 0, w, 0, h, w);
+            assertTrue(name, similar(resultData,dstData));
+            Log.v("BlendUnit", name + " " + similar(resultData, dstData));
+        }
+
+        // Do the same but passing LaunchOptions
+        int xStart = 10;
+        int xEnd = 20;
+        int yStart = 3;
+        int yEnd = 6;
+        Script.LaunchOptions opt = new Script.LaunchOptions();
+        opt.setX(xStart, xEnd).setY(yStart, yEnd);
+        for (int i = 0; i < 14; i++) {
+            buildSrc(srcData, w, h);
+            buildDst(dstData, w, h);
+            src.copyFromUnchecked(srcData);
+            dst.copyFromUnchecked(dstData);
+            switch (i) {
+                case 0:
                     mBlend.forEachSrc(src, dst, opt);
                     break;
-                case 15:
+                case 1:
                     mBlend.forEachDst(src, dst, opt);
                     break;
-                case 16:
+                case 2:
                     mBlend.forEachSrcOver(src, dst, opt);
                     break;
-                case 17:
+                case 3:
                     mBlend.forEachDstOver(src, dst, opt);
                     break;
-                case 18:
+                case 4:
                     mBlend.forEachSrcIn(src, dst, opt);
                     break;
-                case 19:
+                case 5:
                     mBlend.forEachDstIn(src, dst, opt);
                     break;
-                case 20:
+                case 6:
                     mBlend.forEachSrcOut(src, dst, opt);
                     break;
-                case 21:
+                case 7:
                     mBlend.forEachDstOut(src, dst, opt);
                     break;
-                case 22:
+                case 8:
                     mBlend.forEachSrcAtop(src, dst, opt);
                     break;
-                case 23:
+                case 9:
                     mBlend.forEachDstAtop(src, dst, opt);
                     break;
-                case 24:
+                case 10:
                     mBlend.forEachXor(src, dst, opt);
                     break;
-                case 25:
+                case 11:
                     mBlend.forEachAdd(src, dst, opt);
                     break;
-                case 26:
+                case 12:
                     mBlend.forEachSubtract(src, dst, opt);
                     break;
-                case 27:
+                case 13:
                     mBlend.forEachMultiply(src, dst, opt);
                     break;
             }
             dst.copyTo(resultData);
-            String name = javaBlend(i%14, srcData, dstData);
+            String name = javaBlend(i, srcData, dstData, xStart, xEnd, yStart, yEnd, w);
             assertTrue(name, similar(resultData,dstData));
             Log.v("BlendUnit", name + " " + similar(resultData, dstData));
 
@@ -260,6 +279,18 @@
             srcData[i * 4 + 2] = (byte) 0; // blue
             srcData[i * 4 + 3] = (byte) y; // alpha
         }
+        // Manually set a few known problematic values.
+        // These created problems for SRC_OVER, SRC_ATOP
+        srcData[0] = 230 - 256;
+        srcData[1] = 200 - 256;
+        srcData[2] = 210 - 256;
+        srcData[3] = 7;
+
+        // These created problems for DST_OVER, DST_ATOP,
+        srcData[4] = 230 - 255;
+        srcData[5] = 200 - 256;
+        srcData[6] = 210 - 256;
+        srcData[7] = 245 - 256;
     }
 
     // Build a test pattern to be the destination pattern designed to provide a wide range of values
@@ -273,18 +304,29 @@
             dstData[i * 4 + 2] = (byte) y; // blue
             dstData[i * 4 + 3] = (byte) x; // alpha
         }
+        // Manually set a few known problematic values
+        dstData[0] = 170 - 256;
+        dstData[1] = 180 - 256;
+        dstData[2] = 230 - 256;
+        dstData[3] = 245 - 256;
 
+        dstData[4] = 170 - 256;
+        dstData[5] = 180 - 256;
+        dstData[6] = 230 - 256;
+        dstData[7] = 9;
     }
 
-    public String javaBlend(int type, byte[] src, byte[] dst) {
-
-        for (int i = 0; i < dst.length; i += 4) {
-            byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
-                    dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
-            dst[i] = rgba[0];
-            dst[i + 1] = rgba[1];
-            dst[i + 2] = rgba[2];
-            dst[i + 3] = rgba[3];
+    public String javaBlend(int type, byte[] src, byte[] dst, int xStart, int xEnd, int yStart, int yEnd, int width) {
+        for (int y = yStart; y < yEnd; y++) {
+            for (int x = xStart; x < xEnd; x++) {
+                int i = (y * width + x) * 4;
+                byte[] rgba = func[type].filter(src[i], src[i + 1], src[i + 2], src[i + 3],
+                        dst[i], dst[i + 1], dst[i + 2], dst[i + 3]);
+                dst[i] = rgba[0];
+                dst[i + 1] = rgba[1];
+                dst[i + 2] = rgba[2];
+                dst[i + 3] = rgba[3];
+            }
         }
         return func[type].name;
     }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
index e542f37..6639757 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
@@ -24,7 +24,7 @@
     static final int inX = 307;
     static final int inY = 157;
 
-    private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY) {
+  private void testResize(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY, boolean useOpt) {
 
         Element e = makeElement(dt, vecSize);
 
@@ -42,9 +42,14 @@
         mAllocRef = makeAllocation(outW, outH, e);
         mAllocDst = makeAllocation(outW, outH, e);
 
+        Script.LaunchOptions options = makeClipper(10, 8, 40, 46);
         ScriptIntrinsicResize si = ScriptIntrinsicResize.create(mRS);
         si.setInput(mAllocSrc);
-        si.forEach_bicubic(mAllocRef);
+        if (useOpt) {
+          si.forEach_bicubic(mAllocRef, options);
+        } else {
+          si.forEach_bicubic(mAllocRef);
+        }
 
         ScriptC_intrinsic_resize sr = new ScriptC_intrinsic_resize(mRS);
         sr.set_scaleX((float)w/outW);
@@ -52,44 +57,77 @@
         sr.set_gIn(mAllocSrc);
         sr.set_gWidthIn(w);
         sr.set_gHeightIn(h);
-        if (dt == Element.DataType.UNSIGNED_8) {
+        if (useOpt) {
+          if (dt == Element.DataType.UNSIGNED_8) {
             switch(vecSize) {
-            case 4:
+              case 4:
+                sr.forEach_bicubic_U4(mAllocDst, options);
+                break;
+              case 3:
+                sr.forEach_bicubic_U3(mAllocDst, options);
+                break;
+              case 2:
+                sr.forEach_bicubic_U2(mAllocDst, options);
+                break;
+              case 1:
+                sr.forEach_bicubic_U1(mAllocDst, options);
+                break;
+            }
+          } else {
+            switch(vecSize) {
+              case 4:
+                sr.forEach_bicubic_F4(mAllocDst, options);
+                break;
+              case 3:
+                sr.forEach_bicubic_F3(mAllocDst, options);
+                break;
+              case 2:
+                sr.forEach_bicubic_F2(mAllocDst, options);
+                break;
+              case 1:
+                sr.forEach_bicubic_F1(mAllocDst, options);
+                break;
+            }
+          }
+        } else {
+          if (dt == Element.DataType.UNSIGNED_8) {
+            switch(vecSize) {
+              case 4:
                 sr.forEach_bicubic_U4(mAllocDst);
                 break;
-            case 3:
+              case 3:
                 sr.forEach_bicubic_U3(mAllocDst);
                 break;
-            case 2:
+              case 2:
                 sr.forEach_bicubic_U2(mAllocDst);
                 break;
-            case 1:
+              case 1:
                 sr.forEach_bicubic_U1(mAllocDst);
                 break;
             }
-        } else {
+          } else {
             switch(vecSize) {
-            case 4:
+              case 4:
                 sr.forEach_bicubic_F4(mAllocDst);
                 break;
-            case 3:
+              case 3:
                 sr.forEach_bicubic_F3(mAllocDst);
                 break;
-            case 2:
+              case 2:
                 sr.forEach_bicubic_F2(mAllocDst);
                 break;
-            case 1:
+              case 1:
                 sr.forEach_bicubic_F1(mAllocDst);
                 break;
             }
+          }
         }
 
-
         mVerify.set_gAllowedIntError(1);
         mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
-        if (outW == w && outH == h) {
+        //when scale = 1 and we're copyin the entire input, check with the original.
+        if (outW == w && outH == h && !useOpt) {
             mVerify.set_gAllowedIntError(0);
-            //when scale = 1, check with the original.
             mVerify.invoke_verify(mAllocRef, mAllocSrc, mAllocSrc);
             mVerify.invoke_verify(mAllocDst, mAllocSrc, mAllocSrc);
         }
@@ -101,343 +139,684 @@
 
 
     public void test_U8_4_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_3_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_2_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_1_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_3_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_2_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_U8_1_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_U8_4_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_3_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_2_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_U8_1_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
 
     public void test_F32_4_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_3_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_2_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_1_SCALE10_10_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_20_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_05_inSquare() {
-        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_3_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_2_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, false);
         checkError();
     }
     public void test_F32_1_SCALE10_10_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_20_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE20_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, false);
         checkError();
     }
 
     public void test_F32_4_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_3_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_2_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, false);
         checkError();
     }
     public void test_F32_1_SCALE05_05_inRectangle() {
-        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f);
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, false);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+
+    public void test_F32_4_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE10_10_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE20_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE05_20_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE20_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE05_05_inSquare_opt() {
+        testResize(inX, inX, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 1.f, 1.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE10_10_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 1.f, 1.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE20_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 2.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 2.f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE05_20_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 2.f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 2.f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE20_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 2.f, 0.5f, true);
+        checkError();
+    }
+
+    public void test_F32_4_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 4, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_3_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 3, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_2_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 2, 0.5f, 0.5f, true);
+        checkError();
+    }
+    public void test_F32_1_SCALE05_05_inRectangle_opt() {
+        testResize(inX, inY, Element.DataType.FLOAT_32, 1, 0.5f, 0.5f, true);
         checkError();
     }
 
diff --git a/tests/tests/resourcesloader/OWNERS b/tests/tests/resourcesloader/OWNERS
index 0bccde9..5174db8 100644
--- a/tests/tests/resourcesloader/OWNERS
+++ b/tests/tests/resourcesloader/OWNERS
@@ -1,5 +1,5 @@
 # Bug component: 568761
-rtmitchell@google.com
+zyy@google.com
 chiuwinson@google.com
 toddke@google.com
 patb@google.com
diff --git a/tests/tests/role/Android.bp b/tests/tests/role/Android.bp
index a638561..ae1fdfd 100644
--- a/tests/tests/role/Android.bp
+++ b/tests/tests/role/Android.bp
@@ -43,5 +43,6 @@
     data: [
         ":CtsRoleTestApp",
         ":CtsRoleTestApp28",
+        ":CtsRoleTestApp33WithoutInCallService",
     ],
 }
diff --git a/tests/tests/role/AndroidTest.xml b/tests/tests/role/AndroidTest.xml
index 4a8189f..1794e8b 100644
--- a/tests/tests/role/AndroidTest.xml
+++ b/tests/tests/role/AndroidTest.xml
@@ -20,6 +20,7 @@
 
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
@@ -38,6 +39,7 @@
         <option name="cleanup" value="true" />
         <option name="push" value="CtsRoleTestApp.apk->/data/local/tmp/cts/role/CtsRoleTestApp.apk" />
         <option name="push" value="CtsRoleTestApp28.apk->/data/local/tmp/cts/role/CtsRoleTestApp28.apk" />
+        <option name="push" value="CtsRoleTestApp33WithoutInCallService.apk->/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk" />
     </target_preparer>
 
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
index eb17122..b2dfca9 100644
--- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -54,7 +54,20 @@
                 <data android:scheme="tel" />
             </intent-filter>
         </activity>
-
+        <service
+            android:name=".DialerInCallService"
+            android:permission="android.permission.BIND_INCALL_SERVICE"
+            android:exported="true">
+            <meta-data
+                android:name="android.telecom.IN_CALL_SERVICE_UI"
+                android:value="true"/>
+            <meta-data
+                android:name="android.telecom.IN_CALL_SERVICE_CAR_MODE_UI"
+                android:value="false"/>
+            <intent-filter>
+                <action android:name="android.telecom.InCallService" />
+            </intent-filter>
+        </service>
         <!-- Sms -->
         <activity
             android:name=".SmsSendToActivity"
diff --git a/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp
new file mode 100644
index 0000000..7cce565
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/Android.bp
@@ -0,0 +1,23 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsRoleTestApp33WithoutInCallService",
+    min_sdk_version: "30",
+    target_sdk_version: "33",
+}
diff --git a/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
new file mode 100644
index 0000000..a6504ad
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp33WithoutInCallService/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?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.app.role.cts.app33WithoutInCallService">
+    <application android:label="CtsRoleTestApp33WithoutInCallService">
+        <!-- Dialer -->
+        <activity
+            android:name=".DialerDialActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.intent.action.DIAL" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="tel" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 766a927..4cc198c 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -111,6 +111,11 @@
     private static final String APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_28_PACKAGE_NAME
             + ".ChangeDefaultSmsActivity";
 
+    private static final String APP_33_WITHOUT_INCALLSERVICE_APK_PATH =
+            "/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk";
+    private static final String APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME =
+            "android.app.role.cts.app33WithoutInCallService";
+
     private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
             "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
 
@@ -158,12 +163,14 @@
     public void installApp() throws Exception {
         installPackage(APP_APK_PATH);
         installPackage(APP_28_APK_PATH);
+        installPackage(APP_33_WITHOUT_INCALLSERVICE_APK_PATH);
     }
 
     @After
     public void uninstallApp() throws Exception {
         uninstallPackage(APP_PACKAGE_NAME);
         uninstallPackage(APP_28_PACKAGE_NAME);
+        uninstallPackage(APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME);
     }
 
     @Before
@@ -537,6 +544,28 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testHoldDialerRoleRequirementWithInCallServiceAndSdk()
+            throws Exception {
+        assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
+        // target below sdk 33 without InCallService component can hold dialer role
+        addRoleHolder(
+                RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+        assertIsRoleHolder(
+                RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
+        // target sdk 33 without InCallService component cannot hold dialer role
+        addRoleHolder(
+                RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+        assertIsRoleHolder(
+                RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
+        // target sdk 33 with InCallService component can hold dialer role
+        addRoleHolder(
+                RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+        assertIsRoleHolder(
+                RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
+    }
+
+    @Test
     public void
     targetSdk28AndChangeDefaultSmsForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultSms()
             throws Exception {
diff --git a/tests/tests/safetycenter/Android.bp b/tests/tests/safetycenter/Android.bp
new file mode 100644
index 0000000..dddd03c
--- /dev/null
+++ b/tests/tests/safetycenter/Android.bp
@@ -0,0 +1,47 @@
+//
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSafetyCenterTestCases",
+    defaults: ["mts-target-sdk-version-current"],
+    sdk_version: "test_current",
+    min_sdk_version: "30",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "androidx.test.runner",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+        "kotlin-stdlib",
+        "kotlin-test",
+        "modules-utils-build_system",
+        "truth-prebuilt",
+    ],
+    test_suites: [
+        "cts",
+        "general-tests",
+        "mts-permission",
+    ],
+}
diff --git a/tests/tests/safetycenter/AndroidManifest.xml b/tests/tests/safetycenter/AndroidManifest.xml
new file mode 100644
index 0000000..c457c69
--- /dev/null
+++ b/tests/tests/safetycenter/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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.safetycenter.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner"/>
+    </application>
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for SafetyCenter"
+                     android:targetPackage="android.safetycenter.cts">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
+
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+</manifest>
diff --git a/tests/tests/safetycenter/AndroidTest.xml b/tests/tests/safetycenter/AndroidTest.xml
new file mode 100644
index 0000000..fc74c4a
--- /dev/null
+++ b/tests/tests/safetycenter/AndroidTest.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.
+  -->
+<configuration description="Config for CTS SafetyCenter test cases">
+
+    <!-- TODO(b/207111503): Use Sdk33ModuleController once available, and remove @SdkSuppress
+         annotations -->
+    <object
+        class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController"
+        type="module_controller"/>
+
+    <option name="config-descriptor:metadata" key="component" value="framework"/>
+    <option name="config-descriptor:metadata" key="parameter"
+            value="not_instant_app"/>
+    <option name="config-descriptor:metadata" key="parameter"
+            value="not_multi_abi"/>
+    <option name="config-descriptor:metadata" key="parameter"
+            value="secondary_user"/>
+
+    <option name="test-suite-tag" value="cts"/>
+
+    <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+        <option name="force-skip-system-props"
+                value="true"/> <!-- avoid restarting device -->
+    </target_preparer>
+
+    <target_preparer
+        class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="CtsSafetyCenterTestCases.apk"/>
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.safetycenter.cts"/>
+        <option name="runtime-hint" value="5m"/>
+    </test>
+</configuration>
diff --git a/tests/tests/safetycenter/OWNERS b/tests/tests/safetycenter/OWNERS
new file mode 100644
index 0000000..026d342
--- /dev/null
+++ b/tests/tests/safetycenter/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 1026964
+
+include platform/frameworks/base:/core/java/android/permission/OWNERS
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
new file mode 100644
index 0000000..0adf458
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterActivityTest.kt
@@ -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 android.safetycenter.cts
+
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SAFETY_CENTER
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.support.test.uiautomator.By
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetyCenterActivityTest {
+    private val context: Context = getApplicationContext()
+
+    @Test
+    fun launchActivity_showsSecurityAndPrivacyTitle() {
+        context.startActivity(
+            Intent(ACTION_SAFETY_CENTER).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        )
+
+        // CollapsingToolbar title can't be found by text, so using description instead.
+        waitFindObject(By.desc("Security & Privacy"))
+    }
+}
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
new file mode 100644
index 0000000..4448967
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterDataTest.kt
@@ -0,0 +1,178 @@
+/*
+ * 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.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterStatus
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterDataTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val pendingIntent = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+
+    val status1 = SafetyCenterStatus.Builder()
+            .setTitle("This is my title")
+            .setSummary("This is my summary")
+            .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+    val status2 = SafetyCenterStatus.Builder()
+            .setTitle("This is also my title")
+            .setSummary("This is also my summary")
+            .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+            .build()
+
+    val issue1 = SafetyCenterIssue.Builder("iSsUe_iD_oNe")
+            .setTitle("An issue title")
+            .setSummary("An issue summary")
+            .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+            .build()
+    val issue2 = SafetyCenterIssue.Builder("iSsUe_iD_tWo")
+            .setTitle("Another issue title")
+            .setSummary("Another issue summary")
+            .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+
+    val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+            .setTitle("An entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+            .build()
+    val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+            .setTitle("Another entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+
+    val entryGroup1 = SafetyCenterEntryGroup.Builder("eNtRy_gRoUp_iD")
+            .setTitle("An entry group title")
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .setEntries(listOf(entry2))
+            .build()
+
+    val entryOrGroup1 = SafetyCenterEntryOrGroup(entry1)
+    val entryOrGroup2 = SafetyCenterEntryOrGroup(entryGroup1)
+
+    val data1 = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+    val data2 = SafetyCenterData(status2, listOf(issue2), listOf(entryOrGroup2))
+
+    @Test
+    fun getStatus_returnsStatus() {
+        assertThat(data1.status).isEqualTo(status1)
+        assertThat(data2.status).isEqualTo(status2)
+    }
+
+    @Test
+    fun getIssues_returnsIssues() {
+        assertThat(data1.issues).containsExactly(issue1)
+        assertThat(data2.issues).containsExactly(issue2)
+    }
+
+    @Test
+    fun getEntriesOrGroups_returnsEntriesOrGroups() {
+        assertThat(data1.entriesOrGroups).containsExactly(entryOrGroup1)
+        assertThat(data2.entriesOrGroups).containsExactly(entryOrGroup2)
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(data1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel: Parcel = Parcel.obtain()
+
+        data1.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val fromParcel = SafetyCenterData.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(data1)
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(data1).isEqualTo(data1)
+        assertThat(data1.hashCode()).isEqualTo(data1.hashCode())
+        assertThat(data1.toString()).isEqualTo(data1.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByValue_areEqual() {
+        val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+        val equivalentData = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+
+        assertThat(data).isEqualTo(equivalentData)
+        assertThat(data.hashCode()).isEqualTo(equivalentData.hashCode())
+        assertThat(data.toString()).isEqualTo(equivalentData.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_withEmptyLists_equalByValue_areEqual() {
+        val data = SafetyCenterData(status1, listOf(), listOf())
+        val equivalentData = SafetyCenterData(status1, listOf(), listOf())
+
+        assertThat(data).isEqualTo(equivalentData)
+        assertThat(data.hashCode()).isEqualTo(equivalentData.hashCode())
+        assertThat(data.toString()).isEqualTo(equivalentData.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentStatuses_areNotEqual() {
+        val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+        val differentData = SafetyCenterData(status2, listOf(issue1), listOf(entryOrGroup1))
+
+        assertThat(data).isNotEqualTo(differentData)
+        assertThat(data.toString()).isNotEqualTo(differentData.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentIssues_areNotEqual() {
+        val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+        val differentData = SafetyCenterData(status1, listOf(issue2), listOf(entryOrGroup1))
+
+        assertThat(data).isNotEqualTo(differentData)
+        assertThat(data.toString()).isNotEqualTo(differentData.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentEntriesOrGroups_areNotEqual() {
+        val data = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup1))
+        val differentData = SafetyCenterData(status1, listOf(issue1), listOf(entryOrGroup2))
+
+        assertThat(data).isNotEqualTo(differentData)
+        assertThat(data.toString()).isNotEqualTo(differentData.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
new file mode 100644
index 0000000..ae6ed3f
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryGroupTest.kt
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryGroupTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val pendingIntent = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+
+    val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+            .setTitle("An entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+            .build()
+    val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+            .setTitle("Another entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+
+    val groupId1 = "gRoUp_iD_oNe"
+    val groupId2 = "gRoUp_iD_tWo"
+
+    val entryGroup1 = SafetyCenterEntryGroup.Builder(groupId1)
+            .setTitle("A group title")
+            .setSummary("A group summary")
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+            .setEntries(listOf(entry1))
+            .build()
+    val entryGroup2 = SafetyCenterEntryGroup.Builder(groupId2)
+            .setTitle("Another group title")
+            .setSummary("Another group summary")
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .setEntries(listOf(entry2))
+            .build()
+
+    @Test
+    fun getId_returnsId() {
+        assertThat(entryGroup1.id).isEqualTo(groupId1)
+        assertThat(entryGroup2.id).isEqualTo(groupId2)
+    }
+
+    @Test
+    fun getTitle_returnsTitle() {
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setTitle("title one").build().title)
+                .isEqualTo("title one")
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setTitle("title two").build().title)
+                .isEqualTo("title two")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary("one").build().summary)
+                .isEqualTo("one")
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary("two").build().summary)
+                .isEqualTo("two")
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1).setSummary(null).build().summary)
+                .isNull()
+    }
+
+    @Test
+    fun getSeverityLevel_returnsSeverityLevel() {
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_NONE)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_NONE)
+    }
+
+    @Test
+    fun getStatelessIconType_returnsStatelessIconType() {
+        assertThat(entryGroup1.statelessIconType)
+                .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_NONE)
+        assertThat(SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .build()
+                .statelessIconType)
+                .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+    }
+
+    @Test
+    fun getEntries_returnsEntries() {
+        assertThat(entryGroup1.entries).containsExactly(entry1)
+        assertThat(entryGroup2.entries).containsExactly(entry2)
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(entryGroup1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel = Parcel.obtain()
+
+        entryGroup1.writeToParcel(parcel, /* flags= */ 0)
+        parcel.setDataPosition(0)
+
+        val fromParcel = SafetyCenterEntryGroup.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(entryGroup1)
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(entryGroup1).isEqualTo(entryGroup1)
+        assertThat(entryGroup1.hashCode()).isEqualTo(entryGroup1.hashCode())
+        assertThat(entryGroup1.toString()).isEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByValue_areEqual() {
+        val equivalentToEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1).build()
+
+        assertThat(equivalentToEntryGroup1).isEqualTo(entryGroup1)
+        assertThat(equivalentToEntryGroup1.hashCode()).isEqualTo(entryGroup1.hashCode())
+        assertThat(equivalentToEntryGroup1.toString()).isEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentIds_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setId("different!")
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentTitles_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setTitle("different!")
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSummaries_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setSummary("different!")
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSeverityLevels_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN)
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentStatelessIconTypes_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentEntries_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setEntries(listOf(entry2))
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentEntries_emptyList_areNotEqual() {
+        val differentFromEntryGroup1 = SafetyCenterEntryGroup.Builder(entryGroup1)
+                .setEntries(listOf())
+                .build()
+
+        assertThat(differentFromEntryGroup1).isNotEqualTo(entryGroup1)
+        assertThat(differentFromEntryGroup1.toString()).isNotEqualTo(entryGroup1.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
new file mode 100644
index 0000000..fe69602
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryOrGroupTest.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import android.safetycenter.SafetyCenterEntryGroup
+import android.safetycenter.SafetyCenterEntryOrGroup
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryOrGroupTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val pendingIntent = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+
+    val entry1 = SafetyCenterEntry.Builder("eNtRy_iD_OnE")
+            .setTitle("An entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+            .build()
+    val entry2 = SafetyCenterEntry.Builder("eNtRy_iD_TwO")
+            .setTitle("Another entry title")
+            .setPendingIntent(pendingIntent)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+
+    val entryGroup1 = SafetyCenterEntryGroup.Builder("gRoUp_iD_oNe")
+            .setTitle("A group title")
+            .setSummary("A group summary")
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+            .setEntries(listOf(entry1))
+            .build()
+    val entryGroup2 = SafetyCenterEntryGroup.Builder("gRoUp_iD_tWo")
+            .setTitle("Another group title")
+            .setSummary("Another group summary")
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+            .setEntries(listOf(entry2))
+            .build()
+
+    val entryOrGroupWithEntry = SafetyCenterEntryOrGroup(entry1)
+    val entryOrGroupWithGroup = SafetyCenterEntryOrGroup(entryGroup1)
+
+    @Test
+    fun getEntry_returnsEntry() {
+        assertThat(entryOrGroupWithEntry.entry).isEqualTo(entry1)
+    }
+
+    @Test
+    fun getEntry_returnsEntry_whenNull() {
+        assertThat(entryOrGroupWithGroup.entry).isNull()
+    }
+
+    @Test
+    fun getEntryGroup_returnsEntryGroup() {
+        assertThat(entryOrGroupWithGroup.entryGroup).isEqualTo(entryGroup1)
+    }
+
+    @Test
+    fun getEntryGroup_returnsEntryGroup_whenNull() {
+        assertThat(entryOrGroupWithEntry.entryGroup).isNull()
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(entryOrGroupWithEntry.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_withEntry_returnsEquivalentObject() {
+        runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+                entryOrGroupWithEntry)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_withGroup_returnsEquivalentObject() {
+        runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+                entryOrGroupWithGroup)
+    }
+
+    fun runCreateFromParcel_withWriteToParcel_withGroup_returnsEquivalentObjectTest(
+        entryOrGroup: SafetyCenterEntryOrGroup
+    ) {
+        val parcel: Parcel = Parcel.obtain()
+
+        entryOrGroup.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val fromParcel = SafetyCenterEntryOrGroup.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(entryOrGroup)
+    }
+
+    @Test
+    fun equals_hashCode_toString_whenEqualByReference_areEqual() {
+        assertThat(entryOrGroupWithEntry).isEqualTo(entryOrGroupWithEntry)
+        assertThat(entryOrGroupWithEntry.hashCode()).isEqualTo(entryOrGroupWithEntry.hashCode())
+        assertThat(entryOrGroupWithEntry.toString()).isEqualTo(entryOrGroupWithEntry.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_whenEqualByValue_withEntry_areEqual() {
+        val entryOrGroup = SafetyCenterEntryOrGroup(entry1)
+        val equivalentEntryOrGroup = SafetyCenterEntryOrGroup(entry1)
+
+        assertThat(entryOrGroup).isEqualTo(equivalentEntryOrGroup)
+        assertThat(entryOrGroup.hashCode()).isEqualTo(equivalentEntryOrGroup.hashCode())
+        assertThat(entryOrGroup.toString()).isEqualTo(equivalentEntryOrGroup.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_whenEqualByValue_withGroup_areEqual() {
+        val entryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+        val equivalentEntryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+
+        assertThat(entryOrGroup).isEqualTo(equivalentEntryOrGroup)
+        assertThat(entryOrGroup.hashCode()).isEqualTo(equivalentEntryOrGroup.hashCode())
+        assertThat(entryOrGroup.toString()).isEqualTo(equivalentEntryOrGroup.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentEntryAndGroup_areNotEqual() {
+        assertThat(entryOrGroupWithEntry).isNotEqualTo(entryOrGroupWithGroup)
+        assertThat(entryOrGroupWithEntry.toString()).isNotEqualTo(entryOrGroupWithGroup.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentEntries_areNotEqual() {
+        val entryOrGroup = SafetyCenterEntryOrGroup(entry1)
+        val differentEntryOrGroup = SafetyCenterEntryOrGroup(entry2)
+
+        assertThat(entryOrGroup).isNotEqualTo(differentEntryOrGroup)
+        assertThat(entryOrGroup.toString()).isNotEqualTo(differentEntryOrGroup.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentGroups_areNotEqual() {
+        val entryOrGroup = SafetyCenterEntryOrGroup(entryGroup1)
+        val differentEntryOrGroup = SafetyCenterEntryOrGroup(entryGroup2)
+
+        assertThat(entryOrGroup).isNotEqualTo(differentEntryOrGroup)
+        assertThat(entryOrGroup.toString()).isNotEqualTo(differentEntryOrGroup.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
new file mode 100644
index 0000000..9d36faf
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterEntryTest.kt
@@ -0,0 +1,333 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterEntry
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterEntryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val pendingIntent1 = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+    private val pendingIntent2 = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Different Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+
+    private val iconAction1 = SafetyCenterEntry.IconAction(
+            SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+    private val iconAction2 = SafetyCenterEntry.IconAction(
+            SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+
+    private val entry1 = SafetyCenterEntry.Builder("eNtRy_iD")
+            .setTitle("a title")
+            .setSummary("a summary")
+            .setPendingIntent(pendingIntent1)
+            .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN)
+            .setIconAction(iconAction1)
+            .build()
+
+    @Test
+    fun getId_returnsId() {
+        assertThat(SafetyCenterEntry.Builder(entry1).setId("id_one").build().id)
+                .isEqualTo("id_one")
+        assertThat(SafetyCenterEntry.Builder(entry1).setId("id_two").build().id)
+                .isEqualTo("id_two")
+    }
+
+    @Test
+    fun getTitle_returnsTitle() {
+        assertThat(SafetyCenterEntry.Builder(entry1).setTitle("a title").build().title)
+                .isEqualTo("a title")
+        assertThat(SafetyCenterEntry.Builder(entry1).setTitle("another title").build().title)
+                .isEqualTo("another title")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        assertThat(SafetyCenterEntry.Builder(entry1).setSummary("a summary").build().summary)
+                .isEqualTo("a summary")
+        assertThat(SafetyCenterEntry.Builder(entry1).setSummary("another summary").build().summary)
+                .isEqualTo("another summary")
+        assertThat(SafetyCenterEntry.Builder(entry1).setSummary(null).build().summary)
+                .isNull()
+    }
+
+    @Test
+    fun getSeverityLevel_returnsSeverityLevel() {
+        assertThat(SafetyCenterEntry.Builder(entry1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+        assertThat(SafetyCenterEntry.Builder(entry1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+    }
+
+    @Test
+    fun getStatelessIconType_returnsStatelessIconType() {
+        assertThat(entry1.statelessIconType).isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_NONE)
+        assertThat(SafetyCenterEntry.Builder(entry1)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .build()
+                .statelessIconType)
+                .isEqualTo(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+    }
+
+    @Test
+    fun getPendingIntent_returnsPendingIntent() {
+        assertThat(SafetyCenterEntry.Builder(entry1)
+                .setPendingIntent(pendingIntent1)
+                .build()
+                .pendingIntent)
+                .isEqualTo(pendingIntent1)
+        assertThat(SafetyCenterEntry.Builder(entry1)
+                .setPendingIntent(pendingIntent2)
+                .build()
+                .pendingIntent)
+                .isEqualTo(pendingIntent2)
+    }
+
+    @Test
+    fun getIconAction_returnsIconAction() {
+        assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(iconAction1).build().iconAction)
+                .isEqualTo(iconAction1)
+        assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(iconAction2).build().iconAction)
+                .isEqualTo(iconAction2)
+        assertThat(SafetyCenterEntry.Builder(entry1).setIconAction(null).build().iconAction)
+                .isNull()
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(entry1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel = Parcel.obtain()
+
+        entry1.writeToParcel(parcel, /* flags= */ 0)
+        parcel.setDataPosition(0)
+
+        val fromParcel = SafetyCenterEntry.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(entry1)
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(entry1).isEqualTo(entry1)
+        assertThat(entry1.hashCode()).isEqualTo(entry1.hashCode())
+        assertThat(entry1.toString()).isEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByValue_areEqual() {
+        val entry = SafetyCenterEntry.Builder("id")
+                .setTitle("a title")
+                .setSummary("a summary")
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .setPendingIntent(pendingIntent1)
+                .setIconAction(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+                .build()
+        val equivalentEntry = SafetyCenterEntry.Builder("id")
+                .setTitle("a title")
+                .setSummary("a summary")
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .setPendingIntent(pendingIntent1)
+                .setIconAction(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent2)
+                .build()
+
+        assertThat(entry).isEqualTo(equivalentEntry)
+        assertThat(entry.hashCode()).isEqualTo(equivalentEntry.hashCode())
+        assertThat(entry.toString()).isEqualTo(equivalentEntry.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentIds_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setId("a different id")
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentTitles_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setTitle("a different title")
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSummaries_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setSummary("a different summary")
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSeverityLevels_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setSeverityLevel(SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING)
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentStatelessIconTypes_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setStatelessIconType(SafetyCenterEntry.STATELESS_ICON_TYPE_PRIVACY)
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentPendingIntents_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setPendingIntent(pendingIntent2)
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentIconActions_areNotEqual() {
+        val differentFromEntry1 = SafetyCenterEntry.Builder(entry1)
+                .setIconAction(iconAction2)
+                .build()
+
+        assertThat(differentFromEntry1).isNotEqualTo(entry1)
+        assertThat(differentFromEntry1.toString()).isNotEqualTo(entry1.toString())
+    }
+
+    @Test
+    fun iconAction_getType_returnsType() {
+        assertThat(SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+                .type)
+                .isEqualTo(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR)
+        assertThat(SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent1)
+                .type)
+                .isEqualTo(SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO)
+    }
+
+    @Test
+    fun iconAction_getPendingIntent_returnsPendingIntent() {
+        assertThat(SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+                .pendingIntent)
+                .isEqualTo(pendingIntent1)
+        assertThat(SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent2)
+                .pendingIntent)
+                .isEqualTo(pendingIntent2)
+    }
+
+    @Test
+    fun iconAction_describeContents_returns0() {
+        assertThat(iconAction1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun iconAction_createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel = Parcel.obtain()
+
+        iconAction1.writeToParcel(parcel, /* flags= */ 0)
+        parcel.setDataPosition(0)
+
+        val fromParcel = SafetyCenterEntry.IconAction.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(iconAction1)
+    }
+
+    @Test
+    fun iconAction_equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(iconAction1).isEqualTo(iconAction1)
+        assertThat(iconAction1.hashCode()).isEqualTo(iconAction1.hashCode())
+        assertThat(iconAction1.toString()).isEqualTo(iconAction1.toString())
+    }
+
+    @Test
+    fun iconAction_equals_hashCode_toString_equalByValue_areEqual() {
+        val iconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+        val equivalentIconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+
+        assertThat(iconAction).isEqualTo(equivalentIconAction)
+        assertThat(iconAction.hashCode()).isEqualTo(equivalentIconAction.hashCode())
+        assertThat(iconAction.toString()).isEqualTo(equivalentIconAction.toString())
+    }
+
+    @Test
+    fun iconAction_equals_toString_differentTypes_areNotEqual() {
+        val iconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+        val differentIconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO, pendingIntent1)
+
+        assertThat(iconAction).isNotEqualTo(differentIconAction)
+        assertThat(iconAction.toString()).isNotEqualTo(differentIconAction.toString())
+    }
+
+    @Test
+    fun intentAction_equals_toString_differentPendingIntents_areNotEqual() {
+        val iconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent1)
+        val differentIconAction = SafetyCenterEntry.IconAction(
+                SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR, pendingIntent2)
+
+        assertThat(iconAction).isNotEqualTo(differentIconAction)
+        assertThat(iconAction.toString()).isNotEqualTo(differentIconAction.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
new file mode 100644
index 0000000..b8820fb
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterIssueTest.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import android.os.Parcel
+import android.safetycenter.SafetyCenterIssue
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterIssueTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
+    private val pendingIntent1 = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+    private val pendingIntent2 = PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            Intent("Fake Different Data"),
+            PendingIntent.FLAG_IMMUTABLE)
+
+    val action1 = SafetyCenterIssue.Action.Builder()
+            .setLabel("an action")
+            .setPendingIntent(pendingIntent1)
+            .setSuccessMessage("a success message")
+            .build()
+    val action2 = SafetyCenterIssue.Action.Builder()
+            .setLabel("another action")
+            .setPendingIntent(pendingIntent2)
+            .build()
+
+    val issue1 = SafetyCenterIssue.Builder("iSsUe_iD")
+            .setTitle("Everything's good")
+            .setSubtitle("In the neighborhood")
+            .setSummary("Please acknowledge this")
+            .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+            .setActions(listOf(action1))
+            .build()
+
+    @Test
+    fun getId_returnsId() {
+        assertThat(SafetyCenterIssue.Builder(issue1).setId("an id").build().id)
+                .isEqualTo("an id")
+        assertThat(SafetyCenterIssue.Builder(issue1).setId("another id").build().id)
+                .isEqualTo("another id")
+    }
+
+    @Test
+    fun getTitle_returnsTitle() {
+        assertThat(SafetyCenterIssue.Builder(issue1).setTitle("a title").build().title)
+                .isEqualTo("a title")
+        assertThat(SafetyCenterIssue.Builder(issue1).setTitle("another title").build().title)
+                .isEqualTo("another title")
+    }
+
+    @Test
+    fun getSubtitle_returnsSubtitle() {
+        assertThat(SafetyCenterIssue.Builder(issue1).setSubtitle("a subtitle").build().subtitle)
+                .isEqualTo("a subtitle")
+        assertThat(
+                SafetyCenterIssue.Builder(issue1).setSubtitle("another subtitle").build().subtitle)
+                .isEqualTo("another subtitle")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        assertThat(SafetyCenterIssue.Builder(issue1).setSummary("a summary").build().summary)
+                .isEqualTo("a summary")
+        assertThat(SafetyCenterIssue.Builder(issue1).setSummary("another summary").build().summary)
+                .isEqualTo("another summary")
+    }
+
+    @Test
+    fun getSeverityLevel_returnsSeverityLevel() {
+        assertThat(SafetyCenterIssue.Builder(issue1)
+                .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION)
+        assertThat(SafetyCenterIssue.Builder(issue1)
+                .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+                .build()
+                .severityLevel)
+                .isEqualTo(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+    }
+
+    @Test
+    fun isDismissible_returnsIsDismissible() {
+        assertThat(SafetyCenterIssue.Builder(issue1).setDismissible(true).build().isDismissible)
+                .isTrue()
+        assertThat(SafetyCenterIssue.Builder(issue1).setDismissible(false).build().isDismissible)
+                .isFalse()
+    }
+
+    @Test
+    fun shouldConfirmDismissal_returnsShouldConfirmDismissal() {
+        assertThat(SafetyCenterIssue.Builder(issue1)
+                .setShouldConfirmDismissal(true)
+                .build()
+                .shouldConfirmDismissal())
+                .isTrue()
+        assertThat(SafetyCenterIssue.Builder(issue1)
+                .setShouldConfirmDismissal(false)
+                .build()
+                .shouldConfirmDismissal())
+                .isFalse()
+    }
+
+    @Test
+    fun getActions_returnsActions() {
+        assertThat(SafetyCenterIssue.Builder(issue1)
+                .setActions(listOf(action1, action2))
+                .build().actions)
+                .containsExactly(action1, action2)
+        assertThat(SafetyCenterIssue.Builder(issue1).setActions(listOf(action2)).build().actions)
+                .containsExactly(action2)
+        assertThat(SafetyCenterIssue.Builder(issue1).setActions(listOf()).build().actions)
+                .isEmpty()
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(issue1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel = Parcel.obtain()
+
+        issue1.writeToParcel(parcel, /* flags= */ 0)
+        parcel.setDataPosition(0)
+
+        val fromParcel = SafetyCenterIssue.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(issue1)
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(issue1).isEqualTo(issue1)
+        assertThat(issue1.hashCode()).isEqualTo(issue1.hashCode())
+        assertThat(issue1.toString()).isEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByValue_areEqual() {
+        val issue = SafetyCenterIssue.Builder("an id")
+                .setTitle("a title")
+                .setSubtitle("In the neighborhood")
+                .setSummary("Please acknowledge this")
+                .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
+                .setActions(listOf(action1))
+                .build()
+    }
+
+    @Test
+    fun equals_toString_differentIds_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setId("a different id")
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentTitles_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setTitle("a different title")
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentSubtitles_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setSubtitle("a different subtitle")
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentSummaries_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setSummary("a different summary")
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentSeverityLevels_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setSeverityLevel(SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING)
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentIsDismissibleValues_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setDismissible(true)
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentShouldConfirmDismissalValues_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setShouldConfirmDismissal(true)
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun equals_toString_differentActions_areNotEqual() {
+        val differentFromIssue1 = SafetyCenterIssue.Builder(issue1)
+                .setActions(listOf(action2))
+                .build()
+
+        assertThat(differentFromIssue1).isNotEqualTo(issue1)
+        assertThat(differentFromIssue1.toString()).isNotEqualTo(issue1.toString())
+    }
+
+    @Test
+    fun action_getLabel_returnsLabel() {
+        assertThat(action1.label).isEqualTo("an action")
+        assertThat(action2.label).isEqualTo("another action")
+    }
+
+    @Test
+    fun action_getPendingIntent_returnsPendingIntent() {
+        assertThat(action1.pendingIntent).isEqualTo(pendingIntent1)
+        assertThat(action2.pendingIntent).isEqualTo(pendingIntent2)
+    }
+
+    @Test
+    fun action_getSuccessMessage_returnsSuccessMessage() {
+        assertThat(action1.successMessage).isEqualTo("a success message")
+        assertThat(action2.successMessage).isNull()
+    }
+
+    @Test
+    fun action_describeContents_returns0() {
+        assertThat(action1.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun action_createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel = Parcel.obtain()
+
+        action1.writeToParcel(parcel, /* flags= */ 0)
+
+        parcel.setDataPosition(0)
+        val fromParcel = SafetyCenterIssue.Action.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(action1)
+    }
+
+    @Test
+    fun action_equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(action1).isEqualTo(action1)
+        assertThat(action1.hashCode()).isEqualTo(action1.hashCode())
+        assertThat(action1.toString()).isEqualTo(action1.toString())
+    }
+
+    @Test
+    fun action_equals_hashCode_toString_equalByValue_areEqual() {
+        val action = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+        val equivalentAction = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+
+        assertThat(action).isEqualTo(equivalentAction)
+        assertThat(action.toString()).isEqualTo(equivalentAction.toString())
+    }
+
+    @Test
+    fun action_equals_toString_differentLabels_areNotEqual() {
+        val action = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+        val differentAction = SafetyCenterIssue.Action.Builder()
+                .setLabel("a different label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+
+        assertThat(action).isNotEqualTo(differentAction)
+        assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+    }
+
+    @Test
+    fun action_equals_toString_differentPendingIntents_areNotEqual() {
+        val action = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+        val differentAction = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent2)
+                .setSuccessMessage("a success message")
+                .build()
+
+        assertThat(action).isNotEqualTo(differentAction)
+        assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+    }
+
+    @Test
+    fun action_equals_toString_differentSuccessMessages_areNotEqual() {
+        val action = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a success message")
+                .build()
+        val differentAction = SafetyCenterIssue.Action.Builder()
+                .setLabel("a label")
+                .setPendingIntent(pendingIntent1)
+                .setSuccessMessage("a different success message")
+                .build()
+
+        assertThat(action).isNotEqualTo(differentAction)
+        assertThat(action.toString()).isNotEqualTo(differentAction.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
new file mode 100644
index 0000000..a5c7dd8
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt
@@ -0,0 +1,231 @@
+/*
+ * 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.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.content.Intent.ACTION_SAFETY_CENTER
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.res.Resources
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.provider.DeviceConfig
+import android.safetycenter.SafetyCenterManager
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_CRITICAL_WARNING
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import kotlin.test.assertFailsWith
+
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetyCenterManagerTest {
+    private val context: Context = getApplicationContext()
+    private val safetyCenterManager = context.getSystemService(SafetyCenterManager::class.java)!!
+    private val somePendingIntent = PendingIntent.getActivity(
+        context, 0 /* requestCode */,
+        Intent(ACTION_SAFETY_CENTER).addFlags(FLAG_ACTIVITY_NEW_TASK),
+        FLAG_IMMUTABLE
+    )
+
+    @Before
+    @After
+    fun clearSafetyCenterDataBetweenTest() {
+        runWithShellPermissionIdentity {
+            safetyCenterManager.clearSafetyCenterData()
+        }
+    }
+
+    @Test
+    fun getLastSafetyCenterUpdate_noUpdate_returnsNull() {
+        val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+            safetyCenterManager.getLastSafetyCenterUpdate("some_unknown_id")
+        }
+
+        assertThat(lastSafetyCenterUpdate).isNull()
+    }
+
+    @Test
+    fun sendSafetyCenterUpdate_getLastSafetyCenterUpdateReturnsNewValue() {
+        val id = "some_known_id"
+        val safetyCenterUpdate = SafetySourceData.Builder(id).build()
+        runWithShellPermissionIdentity {
+            safetyCenterManager.sendSafetyCenterUpdate(safetyCenterUpdate)
+        }
+
+        val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+            safetyCenterManager.getLastSafetyCenterUpdate(id)
+        }
+
+        assertThat(lastSafetyCenterUpdate).isEqualTo(safetyCenterUpdate)
+    }
+
+    @Test
+    fun sendSafetyCenterUpdate_withSameId_replacesValue() {
+        val id = "some_known_id"
+        val firstSafetyCenterUpdate = SafetySourceData.Builder(id).build()
+        runWithShellPermissionIdentity {
+            safetyCenterManager.sendSafetyCenterUpdate(firstSafetyCenterUpdate)
+        }
+        val secondSafetyCenterUpdate = SafetySourceData.Builder(id).setStatus(
+            SafetySourceStatus.Builder(
+                "Status title", "Summary of the status", STATUS_LEVEL_CRITICAL_WARNING,
+                somePendingIntent
+            ).build()
+        ).addIssue(
+            SafetySourceIssue.Builder(
+                "Issue id", "Issue title", "Summary of the issue",
+                SEVERITY_LEVEL_CRITICAL_WARNING
+            ).addAction(
+                SafetySourceIssue.Action.Builder(
+                    "Solve issue",
+                    somePendingIntent
+                ).build()
+            ).build()
+        ).build()
+        runWithShellPermissionIdentity {
+            safetyCenterManager.sendSafetyCenterUpdate(secondSafetyCenterUpdate)
+        }
+
+        val lastSafetyCenterUpdate = callWithShellPermissionIdentity {
+            safetyCenterManager.getLastSafetyCenterUpdate(id)
+        }
+
+        assertThat(lastSafetyCenterUpdate).isEqualTo(secondSafetyCenterUpdate)
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_whenConfigEnabled_andFlagEnabled_returnsTrue() {
+        if (!deviceSupportsSafetyCenter()) {
+            return
+        }
+
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_SAFETY_CENTER_ENABLED,
+                /* value = */ true.toString(),
+                /* makeDefault = */ false
+            )
+        }
+
+        val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+
+        assertThat(isSafetyCenterEnabled).isTrue()
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_whenConfigEnabled_andFlagDisabled_returnsFalse() {
+        if (!deviceSupportsSafetyCenter()) {
+            return
+        }
+
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_SAFETY_CENTER_ENABLED,
+                /* value = */ false.toString(),
+                /* makeDefault = */ false
+            )
+        }
+
+        val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+
+        assertThat(isSafetyCenterEnabled).isFalse()
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_whenConfigDisabled_andFlagEnabled_returnsFalse() {
+        if (deviceSupportsSafetyCenter()) {
+            return
+        }
+
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_SAFETY_CENTER_ENABLED,
+                /* value = */ true.toString(),
+                /* makeDefault = */ false
+            )
+        }
+
+        val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+
+        assertThat(isSafetyCenterEnabled).isFalse()
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_whenConfigDisabled_andFlagDisabled_returnsFalse() {
+        if (deviceSupportsSafetyCenter()) {
+            return
+        }
+
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(
+                DeviceConfig.NAMESPACE_PRIVACY,
+                PROPERTY_SAFETY_CENTER_ENABLED,
+                /* value = */ false.toString(),
+                /* makeDefault = */ false
+            )
+        }
+
+        val isSafetyCenterEnabled = callWithShellPermissionIdentity {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+
+        assertThat(isSafetyCenterEnabled).isFalse()
+    }
+
+    @Test
+    fun isSafetyCenterEnabled_whenAppDoesntHoldPermission_methodThrows() {
+        assertFailsWith(SecurityException::class) {
+            safetyCenterManager.isSafetyCenterEnabled
+        }
+    }
+
+    private fun deviceSupportsSafetyCenter() =
+        context.resources.getBoolean(
+            Resources.getSystem().getIdentifier(
+                "config_enableSafetyCenter",
+                "bool",
+                "android"
+            )
+        )
+
+    companion object {
+        /** Name of the flag that determines whether SafetyCenter is enabled. */
+        const val PROPERTY_SAFETY_CENTER_ENABLED = "safety_center_is_enabled"
+    }
+}
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
new file mode 100644
index 0000000..c069085
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetyCenterStatusTest.kt
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.safetycenter.cts
+
+import android.os.Parcel
+import android.safetycenter.SafetyCenterStatus
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SafetyCenterStatusTest {
+
+    val baseStatus = SafetyCenterStatus.Builder()
+            .setTitle("This is my title")
+            .setSummary("This is my summary")
+            .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION)
+            .build()
+
+    @Test
+    fun getTitle_returnsTitle() {
+        assertThat(SafetyCenterStatus.Builder(baseStatus).setTitle("title").build().title)
+                .isEqualTo("title")
+
+        assertThat(SafetyCenterStatus.Builder(baseStatus).setTitle("different title").build().title)
+                .isEqualTo("different title")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        assertThat(SafetyCenterStatus.Builder(baseStatus).setSummary("summary").build().summary)
+                .isEqualTo("summary")
+
+        assertThat(
+                SafetyCenterStatus.Builder(baseStatus)
+                        .setSummary("different summary")
+                        .build()
+                        .summary)
+                .isEqualTo("different summary")
+    }
+
+    @Test
+    fun getSeverityLevel_returnsSeverityLevel() {
+        assertThat(
+                SafetyCenterStatus.Builder(baseStatus)
+                        .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+                        .build()
+                        .severityLevel)
+                .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+
+        assertThat(
+                SafetyCenterStatus.Builder(baseStatus)
+                        .setSeverityLevel(
+                                SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+                        .build()
+                        .severityLevel)
+                .isEqualTo(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING)
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        assertThat(baseStatus.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsEquivalentObject() {
+        val parcel: Parcel = Parcel.obtain()
+        baseStatus.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val fromParcel = SafetyCenterStatus.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(fromParcel).isEqualTo(baseStatus)
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByReference_areEqual() {
+        assertThat(baseStatus).isEqualTo(baseStatus)
+        assertThat(baseStatus.hashCode()).isEqualTo(baseStatus.hashCode())
+        assertThat(baseStatus.toString()).isEqualTo(baseStatus.toString())
+    }
+
+    @Test
+    fun equals_hashCode_toString_equalByValue_areEqual() {
+        val status = SafetyCenterStatus.Builder()
+                .setTitle("same title")
+                .setSummary("same summary")
+                .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+                .build()
+        val equivalentStatus = SafetyCenterStatus.Builder()
+                .setTitle("same title")
+                .setSummary("same summary")
+                .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+                .build()
+
+        assertThat(status).isEqualTo(equivalentStatus)
+        assertThat(status.hashCode()).isEqualTo(equivalentStatus.hashCode())
+        assertThat(status.toString()).isEqualTo(equivalentStatus.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentTitles_areNotEqual() {
+        val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+                .setTitle("that's discarsting")
+                .build()
+
+        assertThat(unequalStatus).isNotEqualTo(baseStatus)
+        assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSummaries_areNotEqual() {
+        val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+                .setSummary("discarsting sheet")
+                .build()
+
+        assertThat(unequalStatus).isNotEqualTo(baseStatus)
+        assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+    }
+
+    @Test
+    fun equals_toString_withDifferentSeverityLevels_arNotEqual() {
+        val unequalStatus = SafetyCenterStatus.Builder(baseStatus)
+                .setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK)
+                .build()
+
+        assertThat(unequalStatus).isNotEqualTo(baseStatus)
+        assertThat(unequalStatus.toString()).isNotEqualTo(baseStatus.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
new file mode 100644
index 0000000..0f69501
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceDataTest.kt
@@ -0,0 +1,285 @@
+/*
+ * 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.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceData
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceData]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceDataTest {
+    private val context: Context = getApplicationContext()
+
+    private val status1 = SafetySourceStatus.Builder(
+            "Status title 1",
+            "Status summary 1",
+            SafetySourceStatus.STATUS_LEVEL_NONE,
+            PendingIntent.getActivity(context, 0 /* requestCode= */,
+                    Intent("Status PendingIntent 1"), FLAG_IMMUTABLE))
+            .build()
+    private val status2 = SafetySourceStatus.Builder(
+            "Status title 2",
+            "Status summary 2",
+            SafetySourceStatus.STATUS_LEVEL_RECOMMENDATION,
+            PendingIntent.getActivity(context, 0 /* requestCode= */,
+                    Intent("Status PendingIntent 2"), FLAG_IMMUTABLE))
+            .setIconAction(SafetySourceStatus.IconAction(ICON_TYPE_GEAR,
+                    PendingIntent.getActivity(context, 0 /* requestCode= */,
+                            Intent("IconAction PendingIntent 2"), FLAG_IMMUTABLE)))
+            .build()
+    private val issue1 = SafetySourceIssue.Builder(
+            "Issue id 1",
+            "Issue summary 1",
+            "Issue summary 1",
+            SafetySourceIssue.SEVERITY_LEVEL_INFORMATION)
+            .setSubtitle("Issue subtitle 1")
+            .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+            .addAction(SafetySourceIssue.Action.Builder("Action label 1",
+                    PendingIntent.getActivity(context, 0 /* requestCode= */,
+                            Intent("Issue PendingIntent 1"), FLAG_IMMUTABLE))
+                    .build())
+            .build()
+    private val issue2 = SafetySourceIssue.Builder(
+            "Issue id 2",
+            "Issue title 2",
+            "Issue summary 2",
+            SafetySourceIssue.SEVERITY_LEVEL_RECOMMENDATION)
+            .addAction(SafetySourceIssue.Action.Builder("Action label 2",
+                    PendingIntent.getService(context, 0 /* requestCode= */,
+                            Intent("Issue PendingIntent 2"), FLAG_IMMUTABLE)).build())
+            .setOnDismissPendingIntent(PendingIntent.getService(context,
+                    0 /* requestCode= */,
+                    Intent("Issue OnDismissPendingIntent 2"), FLAG_IMMUTABLE))
+            .build()
+
+    @Test
+    fun getId_returnsId() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+        assertThat(safetySourceData.id).isEqualTo("Safety source id")
+    }
+
+    @Test
+    fun getStatus_withDefaultBuilder_returnsNull() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+        assertThat(safetySourceData.status).isNull()
+    }
+
+    @Test
+    fun getStatus_whenSetExplicitly_returnsStatus() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .build()
+
+        assertThat(safetySourceData.status).isEqualTo(status1)
+    }
+
+    @Test
+    fun getIssues_withDefaultBuilder_returnsEmptyList() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+        assertThat(safetySourceData.issues).isEmpty()
+    }
+
+    @Test
+    fun getIssues_whenSetExplicitly_returnsIssues() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+
+        assertThat(safetySourceData.issues).containsExactly(issue1, issue2).inOrder()
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+
+        assertThat(safetySourceData.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceData() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+
+        val parcel: Parcel = Parcel.obtain()
+        safetySourceData.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val safetySourceDataFromParcel: SafetySourceData =
+                SafetySourceData.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(safetySourceDataFromParcel).isEqualTo(safetySourceData)
+    }
+
+    // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+    @Test
+    fun hashCode_equals_toString_withEqualByReference_withoutStatusAndIssues_areEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id").build()
+        val otherSafetySourceData = safetySourceData
+
+        assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withEqualByReference_withoutIssues_areEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .build()
+        val otherSafetySourceData = safetySourceData
+
+        assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withEqualByReference_areEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+        val otherSafetySourceData = safetySourceData
+
+        assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+
+        assertThat(safetySourceData.hashCode()).isEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentIds_areNotEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id 2")
+                .setStatus(status1)
+                .build()
+
+        assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentIssues_areNotEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status2)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+
+        assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentStatuses_areNotEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .build()
+
+        assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withStatusSetInOneAndNotOther_areNotEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id").build()
+
+        assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withIssuesSetInOneAndNotOther_areNotEqual() {
+        val safetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .addIssue(issue1)
+                .addIssue(issue2)
+                .build()
+        val otherSafetySourceData = SafetySourceData.Builder("Safety source id")
+                .setStatus(status1)
+                .build()
+
+        assertThat(safetySourceData.hashCode()).isNotEqualTo(otherSafetySourceData.hashCode())
+        assertThat(safetySourceData).isNotEqualTo(otherSafetySourceData)
+        assertThat(safetySourceData.toString()).isNotEqualTo(otherSafetySourceData.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
new file mode 100644
index 0000000..545da83
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceIssueTest.kt
@@ -0,0 +1,614 @@
+/*
+ * 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.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceIssue
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_DEVICE
+import android.safetycenter.SafetySourceIssue.ISSUE_CATEGORY_GENERAL
+import android.safetycenter.SafetySourceIssue.Action
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceIssue.SEVERITY_LEVEL_INFORMATION
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceIssue]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceIssueTest {
+    private val context: Context = getApplicationContext()
+
+    private val pendingIntent1: PendingIntent = PendingIntent.getActivity(context,
+            0 /* requestCode= */, Intent("PendingIntent 1"), FLAG_IMMUTABLE)
+    private val action1 = Action.Builder("Action label 1", pendingIntent1).build()
+    private val pendingIntent2: PendingIntent = PendingIntent.getActivity(context,
+            0 /* requestCode= */, Intent("PendingIntent 2"), FLAG_IMMUTABLE)
+    private val action2 = Action.Builder("Action label 2", pendingIntent2).build()
+
+    @Test
+    fun action_getLabel_returnsLabel() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+
+        assertThat(action.label).isEqualTo("Action label")
+    }
+
+    @Test
+    fun action_isResolving_withDefaultBuilder_returnsFalse() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+
+        assertThat(action.isResolving).isFalse()
+    }
+
+    @Test
+    fun action_isResolving_whenSetExplicitly_returnsResolving() {
+        val action = Action.Builder("Action label", pendingIntent1)
+                .setResolving(true)
+                .build()
+
+        assertThat(action.isResolving).isTrue()
+    }
+
+    @Test
+    fun action_getPendingIntent_returnsPendingIntent() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+
+        assertThat(action.pendingIntent).isEqualTo(pendingIntent1)
+    }
+
+    @Test
+    fun action_describeContents_returns0() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+
+        assertThat(action.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun action_createFromParcel_withWriteToParcel_returnsOriginalAction() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+
+        val parcel: Parcel = Parcel.obtain()
+        action.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val actionFromParcel: Action = Action.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(actionFromParcel).isEqualTo(action)
+    }
+
+    // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+    @Test
+    fun action_hashCode_equals_toString_withEqualByReferenceActions_areEqual() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+        val otherAction = action
+
+        assertThat(action.hashCode()).isEqualTo(otherAction.hashCode())
+        assertThat(action).isEqualTo(otherAction)
+        assertThat(action.toString()).isEqualTo(otherAction.toString())
+    }
+
+    @Test
+    fun action_hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+        val otherAction = Action.Builder("Action label", pendingIntent1).build()
+
+        assertThat(action.hashCode()).isEqualTo(otherAction.hashCode())
+        assertThat(action).isEqualTo(otherAction)
+        assertThat(action.toString()).isEqualTo(otherAction.toString())
+    }
+
+    @Test
+    fun action_hashCode_equals_toString_withDifferentLabels_areNotEqual() {
+        val action = Action.Builder("Action label", pendingIntent1).build()
+        val otherAction = Action.Builder("Other action label", pendingIntent1).build()
+
+        assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+        assertThat(action).isNotEqualTo(otherAction)
+        assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+    }
+
+    @Test
+    fun action_hashCode_equals_toString_withDifferentResolving_areNotEqual() {
+        val action =
+                Action.Builder("Action label", pendingIntent1).setResolving(false)
+                        .build()
+        val otherAction =
+                Action.Builder("Action label", pendingIntent1).setResolving(true).build()
+
+        assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+        assertThat(action).isNotEqualTo(otherAction)
+        assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+    }
+
+    @Test
+    fun action_hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+        val action = Action.Builder("Action label",
+                PendingIntent.getActivity(context, 0 /* requestCode= */,
+                        Intent("Action PendingIntent"), FLAG_IMMUTABLE))
+                .build()
+        val otherAction = Action.Builder("Action label",
+                PendingIntent.getActivity(context, 0 /* requestCode= */,
+                        Intent("Other action PendingIntent"), FLAG_IMMUTABLE))
+                .build()
+
+        assertThat(action.hashCode()).isNotEqualTo(otherAction.hashCode())
+        assertThat(action).isNotEqualTo(otherAction)
+        assertThat(action.toString()).isNotEqualTo(otherAction.toString())
+    }
+
+    @Test
+    fun getId_returnsId() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.id).isEqualTo("Issue id")
+    }
+
+    @Test
+    fun getTitle_returnsTitle() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.title).isEqualTo("Issue title")
+    }
+
+    @Test
+    fun getSubtitle_withDefaultBuilder_returnsNull() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.subtitle).isNull()
+    }
+
+    @Test
+    fun getSubtitle_whenSetExplicitly_returnsSubtitle() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.subtitle).isEqualTo("Issue subtitle")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.summary).isEqualTo("Issue summary")
+    }
+
+    @Test
+    fun getSeverityLevel_returnsSeverityLevel() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.severityLevel).isEqualTo(SEVERITY_LEVEL_INFORMATION)
+    }
+
+    @Test
+    fun getIssueCategory_withDefaultBuilder_returnsGeneralCategory() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.issueCategory).isEqualTo(ISSUE_CATEGORY_GENERAL)
+    }
+
+    @Test
+    fun getIssueCategory_whenSetExplicitly_returnsIssueCategory() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setIssueCategory(ISSUE_CATEGORY_DEVICE)
+                .build()
+
+        assertThat(safetySourceIssue.issueCategory).isEqualTo(ISSUE_CATEGORY_DEVICE)
+    }
+
+    @Test
+    fun getActions_returnsActions() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .addAction(action2)
+                .build()
+
+        assertThat(safetySourceIssue.actions).containsExactly(action1, action2).inOrder()
+    }
+
+    @Test
+    fun getOnDismissPendingIntent_withDefaultBuilder_returnsNull() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.onDismissPendingIntent).isNull()
+    }
+
+    @Test
+    fun getOnDismissPendingIntent_whenSetExplicitly_returnsOnDismissPendingIntent() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+
+        assertThat(safetySourceIssue.onDismissPendingIntent).isEqualTo(pendingIntent1)
+    }
+
+    @Test
+    fun build_withNoActions_throwsIllegalArgumentException() {
+        val safetySourceIssueBuilder = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+
+        assertThrows("Safety source issue must contain at least 1 action",
+                IllegalArgumentException::class.java) { safetySourceIssueBuilder.build() }
+    }
+
+    @Test
+    fun build_withMoreThanTwoActions_throwsIllegalArgumentException() {
+        val safetySourceIssueBuilder = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .addAction(action2)
+                .addAction(action1)
+
+        assertThrows("Safety source issue must not contain more than 2 actions",
+                IllegalArgumentException::class.java) { safetySourceIssueBuilder.build() }
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+                .addAction(action1)
+                .addAction(action2)
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+
+        assertThat(safetySourceIssue.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceIssue() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+                .addAction(action1)
+                .addAction(action2)
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+
+        val parcel: Parcel = Parcel.obtain()
+        safetySourceIssue.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val safetySourceIssueFromParcel: SafetySourceIssue =
+                SafetySourceIssue.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(safetySourceIssueFromParcel).isEqualTo(safetySourceIssue)
+    }
+
+    // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+    @Test
+    fun hashCode_equals_toString_withEqualByReferenceSafetySourceIssues_areEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+                .addAction(action1)
+                .addAction(action2)
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+        val otherSafetySourceIssue = safetySourceIssue
+
+        assertThat(safetySourceIssue.hashCode()).isEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+                .addAction(Action.Builder("Action label 1", pendingIntent1)
+                        .setResolving(false)
+                        .build())
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .setIssueCategory(ISSUE_CATEGORY_ACCOUNT)
+                .addAction(Action.Builder("Action label 1", pendingIntent1)
+                        .setResolving(false)
+                        .build())
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentIds_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Other issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentTitles_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Other issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentSubtitles_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Issue subtitle")
+                .addAction(action1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .setSubtitle("Other issue subtitle")
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentSummaries_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Other issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentSeverityLevels_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_CRITICAL_WARNING)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentIssueCategories_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setIssueCategory(ISSUE_CATEGORY_DEVICE)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setIssueCategory(ISSUE_CATEGORY_GENERAL)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentActions_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .addAction(action2)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action2)
+                .addAction(action1)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentOnDismissPendingIntents_areNotEqual() {
+        val safetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setOnDismissPendingIntent(pendingIntent1)
+                .build()
+        val otherSafetySourceIssue = SafetySourceIssue.Builder(
+                "Issue id",
+                "Other issue title",
+                "Issue summary",
+                SEVERITY_LEVEL_INFORMATION)
+                .addAction(action1)
+                .setOnDismissPendingIntent(pendingIntent2)
+                .build()
+
+        assertThat(safetySourceIssue.hashCode()).isNotEqualTo(otherSafetySourceIssue.hashCode())
+        assertThat(safetySourceIssue).isNotEqualTo(otherSafetySourceIssue)
+        assertThat(safetySourceIssue.toString()).isNotEqualTo(otherSafetySourceIssue.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
new file mode 100644
index 0000000..7d9b511
--- /dev/null
+++ b/tests/tests/safetycenter/src/android/safetycenter/cts/SafetySourceStatusTest.kt
@@ -0,0 +1,374 @@
+/*
+ * 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.safetycenter.cts
+
+import android.app.PendingIntent
+import android.app.PendingIntent.FLAG_IMMUTABLE
+import android.content.Context
+import android.content.Intent
+import android.os.Build.VERSION_CODES.TIRAMISU
+import android.os.Parcel
+import android.safetycenter.SafetySourceStatus
+import android.safetycenter.SafetySourceStatus.IconAction
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_GEAR
+import android.safetycenter.SafetySourceStatus.IconAction.ICON_TYPE_INFO
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_CRITICAL_WARNING
+import android.safetycenter.SafetySourceStatus.STATUS_LEVEL_NO_ISSUES
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** CTS tests for [SafetySourceStatus]. */
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = TIRAMISU, codeName = "Tiramisu")
+class SafetySourceStatusTest {
+    private val context: Context = getApplicationContext()
+
+    private val pendingIntent1: PendingIntent = PendingIntent.getActivity(context,
+            0 /* requestCode= */, Intent("PendingIntent 1"), FLAG_IMMUTABLE)
+    private val iconAction1 = IconAction(ICON_TYPE_INFO, pendingIntent1)
+    private val pendingIntent2: PendingIntent = PendingIntent.getActivity(context,
+            0 /* requestCode= */, Intent("PendingIntent 2"), FLAG_IMMUTABLE)
+    private val iconAction2 = IconAction(ICON_TYPE_GEAR, pendingIntent2)
+
+    @Test
+    fun iconAction_getIconType_returnsIconType() {
+        val iconAction = IconAction(ICON_TYPE_INFO, pendingIntent1)
+
+        assertThat(iconAction.iconType).isEqualTo(ICON_TYPE_INFO)
+    }
+
+    @Test
+    fun iconAction_getPendingIntent_returnsPendingIntent() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+        assertThat(iconAction.pendingIntent).isEqualTo(pendingIntent1)
+    }
+
+    @Test
+    fun iconAction_describeContents_returns0() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+        assertThat(iconAction.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun iconAction_createFromParcel_withWriteToParcel_returnsOriginalAction() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+        val parcel: Parcel = Parcel.obtain()
+        iconAction.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val iconActionFromParcel: IconAction = IconAction.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(iconActionFromParcel).isEqualTo(iconAction)
+    }
+
+    // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+    @Test
+    fun iconAction_hashCode_equals_toString_withEqualByReferenceIconActions_areEqual() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+        val otherIconAction = iconAction
+
+        assertThat(iconAction.hashCode()).isEqualTo(otherIconAction.hashCode())
+        assertThat(iconAction).isEqualTo(otherIconAction)
+        assertThat(iconAction.toString()).isEqualTo(otherIconAction.toString())
+    }
+
+    @Test
+    fun iconAction_hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+        val otherIconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+
+        assertThat(iconAction.hashCode()).isEqualTo(otherIconAction.hashCode())
+        assertThat(iconAction).isEqualTo(otherIconAction)
+        assertThat(iconAction.toString()).isEqualTo(otherIconAction.toString())
+    }
+
+    @Test
+    fun iconAction_hashCode_equals_toString_withDifferentIconTypes_areNotEqual() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+        val otherIconAction = IconAction(ICON_TYPE_INFO, pendingIntent1)
+
+        assertThat(iconAction.hashCode()).isNotEqualTo(otherIconAction.hashCode())
+        assertThat(iconAction).isNotEqualTo(otherIconAction)
+        assertThat(iconAction.toString()).isNotEqualTo(otherIconAction.toString())
+    }
+
+    @Test
+    fun iconAction_hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+        val iconAction = IconAction(ICON_TYPE_GEAR, pendingIntent1)
+        val otherIconAction = IconAction(ICON_TYPE_GEAR, pendingIntent2)
+
+        assertThat(iconAction.hashCode()).isNotEqualTo(otherIconAction.hashCode())
+        assertThat(iconAction).isNotEqualTo(otherIconAction)
+        assertThat(iconAction.toString()).isNotEqualTo(otherIconAction.toString())
+    }
+
+    @Test
+    fun getTitle_returnsTitle() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.title).isEqualTo("Status title")
+    }
+
+    @Test
+    fun getSummary_returnsSummary() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.summary).isEqualTo("Status summary")
+    }
+
+    @Test
+    fun getStatusLevel_returnsStatusLevel() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.statusLevel).isEqualTo(STATUS_LEVEL_NO_ISSUES)
+    }
+
+    @Test
+    fun getPendingIntent_returnsPendingIntent() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.pendingIntent).isEqualTo(pendingIntent1)
+    }
+
+    @Test
+    fun getIconAction_withDefaultBuilder_returnsNull() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.iconAction).isNull()
+    }
+
+    @Test
+    fun getIconAction_whenSetExplicitly_returnsIconAction() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+
+        assertThat(safetySourceStatus.iconAction).isEqualTo(iconAction1)
+    }
+
+    @Test
+    fun describeContents_returns0() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+
+        assertThat(safetySourceStatus.describeContents()).isEqualTo(0)
+    }
+
+    @Test
+    fun createFromParcel_withWriteToParcel_returnsOriginalSafetySourceStatus() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+
+        val parcel: Parcel = Parcel.obtain()
+        safetySourceStatus.writeToParcel(parcel, 0 /* flags */)
+        parcel.setDataPosition(0)
+        val safetySourceStatusFromParcel: SafetySourceStatus =
+                SafetySourceStatus.CREATOR.createFromParcel(parcel)
+        parcel.recycle()
+
+        assertThat(safetySourceStatusFromParcel).isEqualTo(safetySourceStatus)
+    }
+
+    // TODO(b/208473675): Use `EqualsTester` for testing `hashcode` and `equals`.
+    @Test
+    fun hashCode_equals_toString_withEqualByReferenceSafetySourceStatuses_areEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+        val otherSafetySourceStatus = safetySourceStatus
+
+        assertThat(safetySourceStatus.hashCode()).isEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withAllFieldsEqual_areEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentTitles_areNotEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Other status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentSummaries_areNotEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Other status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentStatusLevels_areNotEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                pendingIntent1)
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_CRITICAL_WARNING,
+                pendingIntent1)
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentPendingIntents_areNotEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_NO_ISSUES,
+                PendingIntent.getActivity(context, 0 /* requestCode= */,
+                        Intent("Status PendingIntent"), FLAG_IMMUTABLE))
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_CRITICAL_WARNING,
+                PendingIntent.getActivity(context, 0 /* requestCode= */,
+                        Intent("Other status PendingIntent"), FLAG_IMMUTABLE))
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+    }
+
+    @Test
+    fun hashCode_equals_toString_withDifferentIconActions_areNotEqual() {
+        val safetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_CRITICAL_WARNING,
+                pendingIntent1)
+                .setIconAction(iconAction1)
+                .build()
+        val otherSafetySourceStatus = SafetySourceStatus.Builder(
+                "Status title",
+                "Status summary",
+                STATUS_LEVEL_CRITICAL_WARNING,
+                pendingIntent1)
+                .setIconAction(iconAction2)
+                .build()
+
+        assertThat(safetySourceStatus.hashCode()).isNotEqualTo(otherSafetySourceStatus.hashCode())
+        assertThat(safetySourceStatus).isNotEqualTo(otherSafetySourceStatus)
+        assertThat(safetySourceStatus.toString()).isNotEqualTo(otherSafetySourceStatus.toString())
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
index 8fe8054..eea7f56 100644
--- a/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
+++ b/tests/tests/security/src/android/security/cts/FlagSlipperyTest.kt
@@ -30,6 +30,7 @@
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
 import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+import android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.rules.ActivityScenarioRule
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -50,8 +51,6 @@
 import java.util.concurrent.LinkedBlockingQueue
 import java.util.Queue
 
-val FLAG_SLIPPERY = 0x20000000 // android.view.WindowManager.LayoutParams.FLAG_SLIPPERY
-
 private fun getViewCenterOnScreen(v: View): Pair<Float, Float> {
     val location = IntArray(2)
     v.getLocationOnScreen(location)
diff --git a/tests/tests/security/src/android/security/cts/MotionEventTest.java b/tests/tests/security/src/android/security/cts/MotionEventTest.java
index c291ee4..dcdfc9b 100644
--- a/tests/tests/security/src/android/security/cts/MotionEventTest.java
+++ b/tests/tests/security/src/android/security/cts/MotionEventTest.java
@@ -39,8 +39,8 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -70,7 +70,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     /**
diff --git a/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
new file mode 100644
index 0000000..4b10c34
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/WallpaperManagerTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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 static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.app.WallpaperManager;
+import android.content.Context;
+import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.net.Uri;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.platform.test.annotations.RequiresDevice;
+import android.util.Log;
+import android.view.Display;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class WallpaperManagerTest {
+    private static final String TAG = "WallpaperManagerSTS";
+
+    private Context mContext;
+    private WallpaperManager mWallpaperManager;
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity(Manifest.permission.SET_WALLPAPER_HINTS,
+                        Manifest.permission.SET_WALLPAPER);
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mWallpaperManager = WallpaperManager.getInstance(mContext);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    // b/204316511
+    @Test
+    @AsbSecurityTest(cveBugId = 204316511)
+    public void testSetDisplayPadding() {
+        Rect validRect = new Rect(1, 1, 1, 1);
+        // This should work, no exception expected
+        mWallpaperManager.setDisplayPadding(validRect);
+
+        Rect negativeRect = new Rect(-1, 0 , 0, 0);
+        try {
+            mWallpaperManager.setDisplayPadding(negativeRect);
+            Assert.fail("setDisplayPadding should fail for a Rect with negative values");
+        } catch (IllegalArgumentException e) {
+            //Expected exception
+        }
+
+        DisplayManager dm = mContext.getSystemService(DisplayManager.class);
+        Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
+        Context windowContext = mContext.createWindowContext(primaryDisplay,
+                TYPE_APPLICATION, null);
+        Display display = windowContext.getDisplay();
+
+        Rect tooWideRect = new Rect(0, 0, display.getMaximumSizeDimension() + 1, 0);
+        try {
+            mWallpaperManager.setDisplayPadding(tooWideRect);
+            Assert.fail("setDisplayPadding should fail for a Rect width larger than "
+                    + display.getMaximumSizeDimension());
+        } catch (IllegalArgumentException e) {
+            //Expected exception
+        }
+
+        Rect tooHighRect = new Rect(0, 0, 0, display.getMaximumSizeDimension() + 1);
+        try {
+            mWallpaperManager.setDisplayPadding(tooHighRect);
+            Assert.fail("setDisplayPadding should fail for a Rect height larger than "
+                    + display.getMaximumSizeDimension());
+        } catch (IllegalArgumentException e) {
+            //Expected exception
+        }
+    }
+
+    @RequiresDevice
+    @Test(expected = IllegalArgumentException.class)
+    @AsbSecurityTest(cveBugId = 204087139)
+    public void testSetMaliciousStream() {
+        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+        mContext.getSystemService(ActivityManager.class).getMemoryInfo(memoryInfo);
+        final long exploitSize = (long) (memoryInfo.totalMem * 0.95);
+        final File maliciousImageFile = generateMaliciousImageFile(exploitSize, memoryInfo);
+        if (maliciousImageFile == null) {
+            throw new IllegalStateException(
+                    "failed generating malicious image file, size=" + exploitSize);
+        }
+        try (InputStream s = mContext.getContentResolver()
+                .openInputStream(Uri.fromFile(maliciousImageFile))) {
+            mWallpaperManager.setStream(
+                    s, null, true, WallpaperManager.FLAG_LOCK | WallpaperManager.FLAG_SYSTEM);
+            throw new IllegalStateException(
+                    "setStream with size " + exploitSize + " shouldn't succeed!");
+        } catch (IOException ex) {
+        } finally {
+            if (maliciousImageFile.exists()) {
+                maliciousImageFile.delete();
+            }
+        }
+    }
+
+    private File generateMaliciousImageFile(long exploitSize, ActivityManager.MemoryInfo memInfo) {
+        File maliciousFile = new File(mContext.getExternalFilesDir(null) + "/exploit.png");
+        try (BufferedOutputStream bos = new BufferedOutputStream(
+                new FileOutputStream(maliciousFile), (int) (exploitSize * 0.01))) {
+            if (!maliciousFile.exists()) {
+                maliciousFile.createNewFile();
+            }
+            Log.v(TAG, "start generating: ram=" + memInfo.totalMem + ", exploit=" + exploitSize);
+            for (long i = 0; i < exploitSize; i++) {
+                bos.write(0xFF);
+            }
+            Log.v(TAG, "Generate successfully!");
+            return maliciousFile;
+        } catch (Exception ex) {
+            Log.w(TAG, "failed at generating malicious image file, ram="
+                    + memInfo.totalMem + ", exploit=" + exploitSize, ex);
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/selinux/OWNERS b/tests/tests/selinux/OWNERS
index 8824b03..20c32c2 100644
--- a/tests/tests/selinux/OWNERS
+++ b/tests/tests/selinux/OWNERS
@@ -1,4 +1,3 @@
 # Bug component: 85141
 jeffv@google.com
 jgalenson@google.com
-nnk@google.com
diff --git a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
index bd21f9f..f0b84e5 100644
--- a/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
+++ b/tests/tests/sensorprivacy/src/android/sensorprivacy/cts/SensorPrivacyBaseTest.kt
@@ -40,6 +40,7 @@
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Assume
@@ -60,6 +61,8 @@
                 "android.sensorprivacy.cts.usemiccamera.action.USE_MIC_CAM"
         const val MIC_CAM_OVERLAY_ACTIVITY_ACTION =
                 "android.sensorprivacy.cts.usemiccamera.overlay.action.USE_MIC_CAM"
+        const val SHOW_OVERLAY_ACTION =
+                "android.sensorprivacy.cts.usemiccamera.action.SHOW_OVERLAY_ACTION"
         const val FINISH_MIC_CAM_ACTIVITY_ACTION =
                 "android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
         const val USE_MIC_EXTRA =
@@ -213,17 +216,20 @@
     }
 
     fun unblockSensorWithDialogAndAssert() {
-        val buttonResId = if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
-            "com.android.systemui:id/bottom_sheet_positive_button"
-        } else {
-            "android:id/button1"
-        }
+        val buttonResId = getDialogPositiveButtonId()
         UiAutomatorUtils.waitFindObject(By.res(buttonResId)).click()
         eventually {
             assertFalse(isSensorPrivacyEnabled())
         }
     }
 
+    private fun getDialogPositiveButtonId() =
+            if (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
+                "com.android.systemui:id/bottom_sheet_positive_button"
+            } else {
+                "android:id/button1"
+            }
+
     @Test
     @AppModeFull(reason = "Uses secondary app, instant apps have no visibility")
     fun testOpNotRunningWhileSensorPrivacyEnabled() {
@@ -335,6 +341,8 @@
     fun testTapjacking() {
         setSensor(true)
         startTestOverlayApp(false)
+        assertNotNull("Dialog never showed",
+                UiAutomatorUtils.waitFindObject(By.res(getDialogPositiveButtonId())))
         val view = UiAutomatorUtils.waitFindObjectOrNull(By.text("This Should Be Hidden"), 10_000)
         assertNull("Overlay should not have shown.", view)
     }
@@ -367,6 +375,8 @@
         context.startActivity(intent)
         // Wait for app to open
         UiAutomatorUtils.waitFindObject(By.textContains(ACTIVITY_TITLE_SNIP))
+
+        context.sendBroadcast(Intent(SHOW_OVERLAY_ACTION))
     }
 
     private fun finishTestApp() {
diff --git a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
index 127564e..4eda205 100644
--- a/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
+++ b/tests/tests/sensorprivacy/test-apps/CtsUseMicOrCameraAndOverlayForSensorPrivacy/src/android/sensorprivacy/cts/usemiccamera/overlay/UseMicCamera.kt
@@ -38,6 +38,8 @@
     companion object {
         const val MIC_CAM_OVERLAY_ACTIVITY_ACTION =
                 "android.sensorprivacy.cts.usemiccamera.overlay.action.USE_MIC_CAM"
+        const val SHOW_OVERLAY_ACTION =
+                "android.sensorprivacy.cts.usemiccamera.action.SHOW_OVERLAY_ACTION"
         const val FINISH_MIC_CAM_ACTIVITY_ACTION =
                 "android.sensorprivacy.cts.usemiccamera.action.FINISH_USE_MIC_CAM"
         const val USE_MIC_EXTRA =
@@ -70,6 +72,16 @@
             }
         }, IntentFilter(FINISH_MIC_CAM_ACTIVITY_ACTION))
 
+        registerReceiver(object : BroadcastReceiver() {
+            override fun onReceive(context: Context?, intent: Intent?) {
+                val intent = Intent(this@UseMicCamera, OverlayActivity::class.java)
+                if (intent.getBooleanExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, false)) {
+                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                }
+                startActivity(intent)
+            }
+        }, IntentFilter(SHOW_OVERLAY_ACTION))
+
         val useMic = intent.getBooleanExtra(USE_MIC_EXTRA, false)
         val useCam = intent.getBooleanExtra(USE_CAM_EXTRA, false)
         if (useMic) {
@@ -80,13 +92,5 @@
                 cam = openCam(this, intent.getBooleanExtra(UseMicCamera.RETRY_CAM_EXTRA, false))
             }, 1000)
         }
-
-        handler.postDelayed({
-            val intent = Intent(this, OverlayActivity::class.java)
-            if (intent.getBooleanExtra(DELAYED_ACTIVITY_NEW_TASK_EXTRA, false)) {
-                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            }
-            startActivity(intent)
-        }, 2000)
     }
 }
diff --git a/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java b/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java
new file mode 100644
index 0000000..85f5e5d
--- /dev/null
+++ b/tests/tests/settings/src/android/settings/cts/AppLocaleSettingsTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.settings.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.Settings;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests to ensure the Activity to handle
+ * {@link Settings#ACTION_APP_LOCALE_SETTINGS}
+ */
+@RunWith(AndroidJUnit4.class)
+public class AppLocaleSettingsTest {
+    @Test
+    public void testAppLocaleSettingsExist() throws RemoteException {
+        final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS);
+        intent.setData(Uri.parse("package:com.my.app"));
+        final ResolveInfo ri = InstrumentationRegistry.getTargetContext()
+                .getPackageManager().resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+        assertTrue(ri != null);
+    }
+}
diff --git a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
index b6103c8..bd7e263 100644
--- a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
+++ b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
@@ -52,14 +52,7 @@
 
     @Before
     public void setUp() throws Exception {
-        // runOnMainSync or SplitController#isSplitSupported will return wrong value for large
-        // screen devices.
-        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
-            @Override
-            public void run() {
-                mIsSplitSupported = SplitController.getInstance().isSplitSupported();
-            }
-        });
+        mIsSplitSupported = SplitController.getInstance().isSplitSupported();
         mDeepLinkIntentResolveInfo = InstrumentationRegistry.getInstrumentation().getContext()
                 .getPackageManager().resolveActivity(
                 new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY),
diff --git a/tests/tests/shortcutmanager/Android.bp b/tests/tests/shortcutmanager/Android.bp
index 776ce70..fab3c5e 100644
--- a/tests/tests/shortcutmanager/Android.bp
+++ b/tests/tests/shortcutmanager/Android.bp
@@ -26,6 +26,9 @@
         "android.test.runner",
         "android.test.base",
     ],
+    static_libs: [
+        "permission-test-util-lib",
+    ],
     srcs: ["src/**/*.java"],
     test_suites: [
         "cts",
diff --git a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
index b1b0ae5..9186338 100644
--- a/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
+++ b/tests/tests/shortcutmanager/common/src/android.content.pm.cts.shortcutmanager.common/ReplyUtil.java
@@ -83,7 +83,8 @@
             }
         };
 
-        context.registerReceiver(resultReceiver, filter);
+        context.registerReceiver(resultReceiver, filter,
+                Context.RECEIVER_EXPORTED_UNAUDITED);
 
         try {
             // Run the code.
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
index 92335e9..7a9c997 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerClientApiTest.java
@@ -25,21 +25,14 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
 
 import android.app.PendingIntent;
-import android.app.appsearch.AppSearchManager;
-import android.app.appsearch.SearchResult;
-import android.app.appsearch.SearchSpec;
 import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageManager;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
-import android.content.pm.Signature;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.ArraySet;
 
 import com.android.compatibility.common.util.CddTest;
 
@@ -49,13 +42,6 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
 
 /**
  * Tests for {@link ShortcutManager} and {@link ShortcutInfo}.
@@ -607,6 +593,54 @@
         testSetDynamicShortcuts_details();
     }
 
+    public void testSetDynamicShortcuts_withCapabilities() {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcutBuilder("s1")
+                            .setShortLabel("title1")
+                            .setIntent(new Intent("main").putExtra("k1", "yyy"))
+                            .addCapabilityBinding("action.intent.START_EXERCISE",
+                                    "exercise.type", list("running", "jogging"))
+                            .addCapabilityBinding("action.intent.START_EXERCISE",
+                                    "exercise.duration", list("10m"))
+                            .build(),
+                    makeShortcutBuilder("s2")
+                            .setShortLabel("title2")
+                            .setIntent(new Intent("main").putExtra("k1", "yyy"))
+                            .addCapabilityBinding("action.intent.STOP_EXERCISE",
+                                    "exercise.type", list("running", "jogging"))
+                            .build(),
+                    makeShortcut("s3", "title3"))));
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertTrue(si.hasCapability("action.intent.START_EXERCISE"));
+                        assertFalse(si.hasCapability("action.intent.STOP_EXERCISE"));
+                        assertEquals(list("running", "jogging"),
+                                si.getCapabilityParameterValues("action.intent.START_EXERCISE",
+                                "exercise.type"));
+                        assertEquals(list("10m"),
+                                si.getCapabilityParameterValues("action.intent.START_EXERCISE",
+                                        "exercise.duration"));
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertFalse(si.hasCapability("action.intent.START_EXERCISE"));
+                        assertTrue(si.hasCapability("action.intent.STOP_EXERCISE"));
+                        assertEquals(list("running", "jogging"),
+                                si.getCapabilityParameterValues("action.intent.STOP_EXERCISE",
+                                        "exercise.type"));
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+        });
+    }
+
     public void testAddDynamicShortcuts() {
         runWithCallerWithStrictMode(mPackageContext1, () -> {
             assertTrue(getManager().addDynamicShortcuts(list(
@@ -2226,6 +2260,127 @@
         });
     }
 
+    public void testSetShortcutsExcludedFromLauncher_ExcludedFromSearchResults() {
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1", "title1"),
+                    makeShortcut("s2", "title2"),
+                    makeShortcut("s3", "title3"),
+                    makeShortcutExcludedFromLauncher("s4"))));
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Publish from different package.
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s1x", "title1x"),
+                    makeShortcutExcludedFromLauncher("seven"))));
+        });
+
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package 1 still has the same shortcuts.
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1", "s2", "s3")
+                    .forShortcutWithId("s1", si -> {
+                        assertEquals("title1", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2", si.getShortLabel());
+                    })
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("title3", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(list(
+                    makeShortcut("s2", "title2-updated"),
+                    makeShortcutExcludedFromLauncher("s4"))));
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s2")
+                    .forShortcutWithId("s2", si -> {
+                        assertEquals("title2-updated", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertTrue(getManager().setDynamicShortcuts(
+                    list(makeShortcutExcludedFromLauncher("s4"))));
+        });
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+
+        // Package2 still has the same shortcuts.
+        runWithCallerWithStrictMode(mPackageContext2, () -> {
+            assertWith(getManager().getDynamicShortcuts())
+                    .areAllEnabled()
+                    .areAllDynamic()
+                    .haveIds("s1x")
+                    .forShortcutWithId("s1x", si -> {
+                        assertEquals("title1x", si.getShortLabel());
+                    });
+            assertWith(getManager().getPinnedShortcuts())
+                    .isEmpty();
+            assertWith(getManager().getManifestShortcuts())
+                    .isEmpty();
+        });
+    }
+
     // TODO Test auto rank adjustment.
     // TODO Test save & load.
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
index 7abd0a2..0e60f4b 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerCtsTestsBase.java
@@ -37,10 +37,14 @@
 import android.os.StrictMode;
 import android.os.StrictMode.ThreadPolicy;
 import android.os.UserHandle;
-import androidx.annotation.NonNull;
+import android.provider.DeviceConfig;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
+import androidx.annotation.NonNull;
+
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -450,6 +454,17 @@
     }
 
     /**
+     * Make a shortcut excluded from launcher with an ID.
+     */
+    protected ShortcutInfo makeShortcutExcludedFromLauncher(String id) {
+        return makeShortcut(
+                id, "Title-" + id, /* activity =*/ null, /* icon =*/ null,
+                makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class), /* rank =*/ 0,
+                /* locusId =*/ null, /* longLived =*/ true,
+                /* excludedSurfaces */ ShortcutInfo.SURFACE_LAUNCHER);
+    }
+
+    /**
      * Make multiple shortcuts with IDs.
      */
     protected List<ShortcutInfo> makeShortcuts(String... ids) {
@@ -482,11 +497,21 @@
      */
     protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
             Icon icon, Intent intent, int rank, LocusId locusId, boolean longLived) {
+        return makeShortcut(id, shortLabel, activity, icon, intent, rank, locusId, longLived, 0);
+    }
+
+    /**
+     * Make a shortcut with details.
+     */
+    protected ShortcutInfo makeShortcut(String id, String shortLabel, ComponentName activity,
+            Icon icon, Intent intent, int rank, LocusId locusId, boolean longLived,
+            int excludedSurfaces) {
         final ShortcutInfo.Builder b = makeShortcutBuilder(id)
                 .setShortLabel(shortLabel)
                 .setRank(rank)
                 .setIntent(intent)
-                .setLongLived(longLived);
+                .setLongLived(longLived)
+                .setExcludedFromSurfaces(excludedSurfaces);
         if (activity != null) {
             b.setActivity(activity);
         } else if (mTargetActivityOverride != null) {
@@ -619,4 +644,10 @@
         }
         return getLauncherApps().getShortcuts(q, getUserHandle());
     }
+
+    protected boolean isAppSearchEnabled() {
+        return SystemUtil.runWithShellPermissionIdentity(() ->
+                DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                        "shortcut_appsearch_integration", false));
+    }
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
index 33b9c90..1b9c48b 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -16,6 +16,7 @@
 package android.content.pm.cts.shortcutmanager;
 
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSISTED_DATA;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
@@ -26,11 +27,16 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
 
 import android.content.LocusId;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.SystemUtil;
+
+import java.util.List;
 
 @SmallTest
 public class ShortcutManagerLauncherApiTest extends ShortcutManagerCtsTestsBase {
@@ -411,4 +417,271 @@
         assertIconDimensions(icon2, getIconAsLauncher(
             mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
     }
+
+    public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            // Verifies setDynamicShortcuts persists shortcuts into AppSearch
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcutExcludedFromLauncher("s3")
+            ));
+            // Verify shortcut excluded from launcher are not included in search result
+            assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+                    .haveIds("s1", "s2")
+                    .areAllDynamic();
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3"),
+                    null);
+            // Package 1
+            assertWith(ret).haveIds("s1", "s2", "s3");
+        });
+    }
+
+    public void testRemoveAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            // Verifies setDynamicShortcuts persists shortcuts into AppSearch
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcutExcludedFromLauncher("s3")
+            ));
+            // Verify shortcut excluded from launcher are not included in search result
+            assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+                    .haveIds("s1", "s2")
+                    .areAllDynamic();
+        });
+        Thread.sleep(5000);
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            // Verifies removeAllDynamicShortcuts removes shortcuts from persistence layer
+            getManager().removeAllDynamicShortcuts();
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3"),
+                    null);
+            assertWith(ret).isEmpty();
+        });
+    }
+
+    public void testAddDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcutExcludedFromLauncher("s3")
+            ));
+            // Verify shortcut excluded from launcher are not included in search result
+            assertWith(manager.getShortcuts(ShortcutManager.FLAG_MATCH_DYNAMIC))
+                    .haveIds("s1", "s2")
+                    .areAllDynamic();
+            // Verifies addDynamicShortcuts persists shortcuts into AppSearch
+            manager.addDynamicShortcuts(list(makeShortcut("s4"), makeShortcut("s5")));
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5");
+        });
+    }
+
+    public void testPushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        SystemUtil.runShellCommand("cmd shortcut override-config max_shortcuts=5");
+        runWithCallerWithStrictMode(mPackageContext1, () ->
+                getManager().setDynamicShortcuts(list(
+                        makeShortcut("s1"),
+                        makeShortcut("s2"),
+                        makeShortcut("s3"),
+                        makeShortcutExcludedFromLauncher("s4"),
+                        makeShortcutExcludedFromLauncher("s5")
+                )));
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5");
+        });
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            // Verifies pushDynamicShortcuts further persists shortcuts into AppSearch without
+            // removing previous shortcuts when max number of shortcuts is reached.
+            getManager().pushDynamicShortcut(makeShortcut("s6"));
+        });
+        Thread.sleep(5000);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5", "s6"),
+                    null);
+            assertWith(ret).haveIds("s1", "s2", "s3", "s4", "s5", "s6");
+        });
+        SystemUtil.runShellCommand("cmd shortcut reset-config");
+    }
+
+    public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcut("s2"),
+                    makeShortcutExcludedFromLauncher("s3"),
+                    makeShortcutExcludedFromLauncher("s4"),
+                    makeShortcutExcludedFromLauncher("s5")
+            ));
+            // Verifies removeDynamicShortcuts removes shortcuts from persistence layer
+            manager.removeDynamicShortcuts(list("s1"));
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret).haveIds("s2", "s3", "s4", "s5");
+        });
+    }
+
+    public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            manager.setDynamicShortcuts(list(
+                    makeShortcutExcludedFromLauncher("s1"),
+                    makeShortcut("s2"),
+                    makeShortcutExcludedFromLauncher("s3"),
+                    makeShortcut("s4"),
+                    makeShortcut("s5")
+            ));
+            manager.removeDynamicShortcuts(list("s2"));
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret).haveIds("s1", "s3", "s4", "s5");
+        });
+    }
+
+    public void testDisableShortcuts_RemovesShortcutsFromDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcutExcludedFromLauncher("s2"),
+                    makeShortcut("s3"),
+                    makeShortcutExcludedFromLauncher("s4"),
+                    makeShortcut("s5")
+            ));
+            // Verifies disableShortcuts removes shortcuts from persistence layer
+            manager.disableShortcuts(list("s3"));
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret).haveIds("s1", "s2", "s4", "s5");
+        });
+    }
+
+    public void testUpdateShortcuts_UpdateShortcutsOnDisk() throws Exception {
+        if (!isAppSearchEnabled()) {
+            return;
+        }
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutManager manager = getManager();
+            manager.setDynamicShortcuts(list(
+                    makeShortcut("s1"),
+                    makeShortcutExcludedFromLauncher("s2"),
+                    makeShortcut("s3"),
+                    makeShortcutExcludedFromLauncher("s4"),
+                    makeShortcut("s5")
+            ));
+            // Verifies shortcuts in persistence layer are being updated
+            manager.updateShortcuts(list(makeShortcut("s3", "custom")));
+        });
+        Thread.sleep(5000);
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+        runWithCallerWithStrictMode(mLauncherContext1, () -> {
+            final List<ShortcutInfo> ret = getShortcutsAsLauncher(
+                    FLAG_GET_PERSISTED_DATA,
+                    mPackageContext1.getPackageName(),
+                    null,
+                    0,
+                    list("s1", "s2", "s3", "s4", "s5"),
+                    null);
+            assertWith(ret)
+                    .haveIds("s1", "s2", "s3", "s4", "s5")
+                    .forShortcutWithId("s3", si -> {
+                        assertEquals("custom", si.getShortLabel());
+                    });
+        });
+    }
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
index d7b506a..79efffb 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerRequestPinTest.java
@@ -16,7 +16,6 @@
 package android.content.pm.cts.shortcutmanager;
 
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertWith;
-import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.dumpsysShortcut;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.retryUntil;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.setDefaultLauncher;
@@ -33,6 +32,7 @@
 import android.content.pm.cts.shortcutmanager.common.ReplyUtil;
 import android.os.PersistableBundle;
 import android.util.Log;
+
 import com.android.compatibility.common.util.CddTest;
 
 import java.util.HashMap;
@@ -43,6 +43,7 @@
     private static final String TAG = "ShortcutMRPT";
 
     private static final String SHORTCUT_ID = "s12345";
+    private static final String HIDDEN_SHORTCUT_ID = "s24680";
 
     @CddTest(requirement="[3.8.1/C-2-1],[3.8.1/C-3-1]")
     public void testIsRequestPinShortcutSupported() {
@@ -351,6 +352,29 @@
         }
     }
 
+    /**
+     * Same as {@link ShortcutManager#requestPinShortcut} except the app has no main activities.
+     */
+    public void testRequestPinShortcutExcludedFromLauncher_ThrowsException() {
+        setDefaultLauncher(getInstrumentation(), mLauncherContext1);
+
+        runWithCallerWithStrictMode(mPackageContext1, () -> {
+            final ShortcutInfo shortcut = makeShortcutBuilder(HIDDEN_SHORTCUT_ID)
+                    .setExcludedFromSurfaces(ShortcutInfo.SURFACE_LAUNCHER)
+                    .build();
+
+            Log.i(TAG, "Calling requestPinShortcut...");
+            boolean isIllegalArgumentExceptionThrown = false;
+            try {
+                assertTrue(getManager().requestPinShortcut(shortcut, /* intent sender */ null));
+            } catch (IllegalArgumentException e) {
+                isIllegalArgumentExceptionThrown = true;
+            }
+            assertTrue(isIllegalArgumentExceptionThrown);
+            Log.i(TAG, "Done.");
+        });
+    }
+
     // TODO Various other cases (already pinned, etc)
     // TODO Various error cases (missing mandatory fields, etc)
 }
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
index 79b6bd1..d1e83f8 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerThrottlingTest.java
@@ -21,10 +21,12 @@
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.resetAllThrottling;
 import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.runCommandForNoOutput;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.cts.shortcutmanager.common.Constants;
 import android.content.pm.cts.shortcutmanager.common.ReplyUtil;
+import android.permission.cts.PermissionUtils;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
@@ -64,6 +66,8 @@
     protected void setUp() throws Exception {
         super.setUp();
 
+        PermissionUtils.grantPermission(TARGET_PACKAGE, Manifest.permission.POST_NOTIFICATIONS);
+
         resetAllThrottling(getInstrumentation());
 
         UiDevice.getInstance(getInstrumentation()).pressHome();
@@ -71,6 +75,12 @@
         runCommandForNoOutput(getInstrumentation(), "am force-stop " + TARGET_PACKAGE);
     }
 
+    @Override
+    protected void tearDown() throws Exception {
+        PermissionUtils.revokePermission(TARGET_PACKAGE, Manifest.permission.POST_NOTIFICATIONS);
+        super.tearDown();
+    }
+
     public void testSetDynamicShortcuts() throws InterruptedException {
         callTest(Constants.TEST_SET_DYNAMIC_SHORTCUTS);
     }
diff --git a/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java b/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
index c3cf011..0b1387a 100644
--- a/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
+++ b/tests/tests/slice/src/android/slice/cts/SlicePermissionsTest.java
@@ -14,7 +14,6 @@
 
 package android.slice.cts;
 
-import android.content.pm.PackageManager;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
@@ -22,8 +21,12 @@
 import static org.junit.Assume.assumeFalse;
 
 import android.app.slice.SliceManager;
+import android.app.slice.SliceProvider;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Process;
 
@@ -35,6 +38,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
+
 @RunWith(AndroidJUnit4.class)
 public class SlicePermissionsTest {
 
@@ -174,6 +179,14 @@
     }
 
     @Test
+    public void testPermissionIntent() {
+        Intent intent = SliceProvider.createPermissionIntent(mContext, BASE_URI, mTestPkg);
+        PackageManager pm = mContext.getPackageManager();
+        List<ResolveInfo> activities = pm.queryIntentActivities(intent, 0);
+        assertEquals(1, activities.size());
+    }
+
+    @Test
     public void testRevokeChild() {
         assumeFalse(isSliceDisabled);
         Uri uri = BASE_URI.buildUpon()
diff --git a/tests/tests/speech/OWNERS b/tests/tests/speech/OWNERS
index c9150c2..e72b790 100644
--- a/tests/tests/speech/OWNERS
+++ b/tests/tests/speech/OWNERS
@@ -1,2 +1,5 @@
 # Bug component: 63521
-rni@google.com
\ No newline at end of file
+volnov@google.com
+eugeniom@google.com
+schfan@google.com
+andreaambu@google.com
\ No newline at end of file
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 740020c..7377132 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -18,12 +18,12 @@
 
 import static android.telecom.Connection.PROPERTY_HIGH_DEF_AUDIO;
 import static android.telecom.Connection.PROPERTY_WIFI;
-import static android.telecom.cts.TestUtils.*;
-
-import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
+import static android.telecom.cts.TestUtils.InvokeCounter;
+import static android.telecom.cts.TestUtils.TEST_PHONE_ACCOUNT_HANDLE;
+import static android.telecom.cts.TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2;
+import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS;
 
 import static org.hamcrest.CoreMatchers.instanceOf;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertThat;
 
 import android.content.Context;
@@ -299,6 +299,9 @@
 
         mConnection.setConnectionProperties(Connection.PROPERTY_CROSS_SIM);
         assertCallProperties(mCall, Call.Details.PROPERTY_CROSS_SIM);
+
+        mConnection.setConnectionProperties(Connection.PROPERTY_TETHERED_CALL);
+        assertCallProperties(mCall, Call.Details.PROPERTY_TETHERED_CALL);
     }
 
     /**
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallTest.java b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
index f7bad4e..1659c12 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallTest.java
@@ -125,4 +125,11 @@
         assertTrue(cas.getSupportedBluetoothDevices().contains(TestUtils.BLUETOOTH_DEVICE1));
         assertTrue(cas.getSupportedBluetoothDevices().contains(TestUtils.BLUETOOTH_DEVICE2));
     }
+
+    public void testExternalCallAudioState() {
+        CallAudioState cas = new CallAudioState(false, CallAudioState.ROUTE_EXTERNAL,
+                CallAudioState.ROUTE_BLUETOOTH + CallAudioState.ROUTE_SPEAKER
+                        + CallAudioState.ROUTE_EARPIECE);
+        assertEquals(CallAudioState.ROUTE_EXTERNAL, cas.getSupportedRouteMask());
+    }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
index a1d9e6d..e91c831 100644
--- a/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
+++ b/tests/tests/telecom/src/android/telecom/cts/EmergencyCallTests.java
@@ -16,13 +16,19 @@
 
 package android.telecom.cts;
 
+import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.CallLog;
 import android.telecom.Call;
 import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
 
-import java.util.concurrent.CountDownLatch;
+import java.util.ArrayList;
+import java.util.Random;
+import java.util.UUID;
 
 public class EmergencyCallTests extends BaseTelecomTestWithMockServices {
 
@@ -37,16 +43,49 @@
     }
 
     /**
+     * Tests a scenario where an emergency call could fail due to the presence of invalid
+     * {@link PhoneAccount} data.
+     * The seed and quantity for {@link #generateRandomPhoneAccounts(long, int)} is chosen to
+     * represent a set of phone accounts which is known in AOSP to cause a failure placing an
+     * emergency call.  {@code 52L} was chosen as a random seed and {@code 50} was chosen as the
+     * set size for {@link PhoneAccount}s as these were observed in repeated test invocations to
+     * induce the failure method.
+     *
+     * @throws Exception
+     */
+    public void testEmergencyCallFailureDueToInvalidPhoneAccounts() throws Exception {
+        if (!mShouldTestTelecom) return;
+
+        ArrayList<PhoneAccount> accounts = generateRandomPhoneAccounts(52L, 50);
+        accounts.stream().forEach(a -> mTelecomManager.registerPhoneAccount(a));
+        try {
+            assertTrue(mTelecomManager.getSelfManagedPhoneAccounts().size() >= 50);
+
+            // The existing start emergency call test is impacted if there is a failure due to
+            // excess phone accounts being present.
+            testStartEmergencyCall();
+        } finally {
+            accounts.stream().forEach(d -> mTelecomManager.unregisterPhoneAccount(
+                    d.getAccountHandle()));
+        }
+    }
+
+    /**
      * Place an outgoing emergency call and ensure it is started successfully.
      */
     public void testStartEmergencyCall() throws Exception {
         if (!mShouldTestTelecom) return;
-        placeAndVerifyEmergencyCall(true /*supportsHold*/);
+        Connection conn = placeAndVerifyEmergencyCall(true /*supportsHold*/);
         Call eCall = getInCallService().getLastCall();
         assertCallState(eCall, Call.STATE_DIALING);
 
         assertIsInCall(true);
         assertIsInManagedCall(true);
+        conn.setActive();
+        conn.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
+        conn.destroy();
+        assertConnectionState(conn, Connection.STATE_DISCONNECTED);
+        assertIsInCall(false);
     }
 
     /**
@@ -147,4 +186,44 @@
         // Notify as missed instead of rejected, since the user did not explicitly reject.
         verifyCallLogging(normalIncomingCallNumber, CallLog.Calls.MISSED_TYPE);
     }
+
+    /**
+     * Generates random phone accounts.
+     * @param seed random seed to use for random UUIDs; passed in for determinism.
+     * @param count How many phone accounts to use.
+     * @return Random phone accounts.
+     */
+    private ArrayList<PhoneAccount> generateRandomPhoneAccounts(long seed, int count) {
+        Random random = new Random(seed);
+        ArrayList<PhoneAccount> accounts = new ArrayList<>();
+        for (int ix = 0 ; ix < count; ix++) {
+            ArrayList<String> supportedSchemes = new ArrayList<>();
+            supportedSchemes.add("tel");
+            supportedSchemes.add("sip");
+            supportedSchemes.add("custom");
+            PhoneAccountHandle handle = new PhoneAccountHandle(new ComponentName(TestUtils.PACKAGE,
+                    TestUtils.COMPONENT), getRandomUuid(random).toString());
+            PhoneAccount acct = new PhoneAccount.Builder(handle, "TelecommTests")
+                    .setAddress(Uri.fromParts("tel", "555-1212", null))
+                    .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+                    .setHighlightColor(0)
+                    .setShortDescription("test_" + ix)
+                    .setSupportedUriSchemes(supportedSchemes)
+                    .build();
+            accounts.add(acct);
+        }
+        return accounts;
+    }
+
+    /**
+     * Returns a random UUID based on the passed in Random generator.
+     * @param random Random generator.
+     * @return The UUID.
+     */
+    private UUID getRandomUuid(Random random) {
+        byte[] array = new byte[16];
+        random.nextBytes(array);
+        return UUID.nameUUIDFromBytes(array);
+    }
+
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
index 0b74a9e..a31e90f 100644
--- a/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
+++ b/tests/tests/telecom/src/android/telecom/cts/TestUtils.java
@@ -335,7 +335,7 @@
         }
         final PackageManager pm = context.getPackageManager();
         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
-                pm.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+                pm.hasSystemFeature(PackageManager.FEATURE_TELECOM);
     }
 
     public static String setCallDiagnosticService(Instrumentation instrumentation,
diff --git a/tests/tests/telephony/README.md b/tests/tests/telephony/README.md
new file mode 100644
index 0000000..f1f1a32
--- /dev/null
+++ b/tests/tests/telephony/README.md
@@ -0,0 +1,3 @@
+# Telephony CTS tests
+
+This directory contains the main set of Telephony CTS tests.
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 0d3b179..cccffdb 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -32,7 +32,8 @@
         "src/android/telephony/ims/cts/ImsUtils.java",
         "src/android/telephony/ims/cts/SipDialogAttributes.java",
         "src/android/telephony/ims/cts/SipMessageUtils.java",
-	    "src/android/telephony/ims/cts/TestAcsClient.java",
+        "src/android/telephony/ims/cts/TestAcsClient.java",
+        "src/android/telephony/ims/cts/TestImsCallSessionImpl.java",
     ],
     path: "src/",
 }
@@ -75,7 +76,7 @@
         local_include_dirs: [
             "EmbmsMiddlewareTestApp/aidl/",
             "TestExternalImsServiceApp/aidl/",
-        ]
+        ],
     },
     test_suites: [
         "cts",
@@ -92,6 +93,5 @@
     name: "cts-telephony-utils",
     srcs: [
         "src/android/telephony/cts/TelephonyUtils.java",
-    ]
+    ],
 }
-
diff --git a/tests/tests/telephony/current/AndroidManifest.xml b/tests/tests/telephony/current/AndroidManifest.xml
index 5eca91d..8befd97 100644
--- a/tests/tests/telephony/current/AndroidManifest.xml
+++ b/tests/tests/telephony/current/AndroidManifest.xml
@@ -17,6 +17,8 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="android.telephony.cts">
 
+    <uses-permission android:name="android.permission.CALL_PHONE"/>
+    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS"/>
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
@@ -106,7 +108,7 @@
             </intent-filter>
         </service>
 
-        <service android:name="android.telephony.cts.StubInCallService"
+        <service android:name="android.telephony.cts.InCallServiceStateValidator"
              android:permission="android.permission.BIND_INCALL_SERVICE"
              android:exported="true">
             <intent-filter>
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
index 2529ada..71924a2 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/aidl/android/telephony/cts/externalimsservice/ITestExternalImsService.aidl
@@ -29,4 +29,5 @@
     boolean isRcsFeatureCreated();
     boolean isMmTelFeatureCreated();
     void resetState();
+    boolean isTelephonyBound();
 }
diff --git a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
index 9101ae5..eaeb6d1 100644
--- a/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
+++ b/tests/tests/telephony/current/TestExternalImsServiceApp/src/android/telephony/cts/externalimsservice/TestExternalImsService.java
@@ -29,7 +29,7 @@
  */
 
 public class TestExternalImsService extends TestImsService {
-    private static final String TAG = "GtsImsTestDeviceImsService";
+    private static final String TAG = "CtsImsTestDeviceImsService";
     // TODO: Use ImsService.SERVICE_INTERFACE definition when it becomes public.
     private static final String ACTION_BIND_IMS_SERVICE = "android.telephony.ims.ImsService";
 
@@ -56,19 +56,26 @@
         public void resetState() {
             TestExternalImsService.this.resetState();
         }
+
+        public boolean isTelephonyBound() {
+            return TestExternalImsService.this.isTelephonyBound();
+        }
     }
 
     @Override
     public IBinder onBind(Intent intent) {
-        if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
-            if (ImsUtils.VDBG) {
-                Log.i(TAG, "onBind-Remote");
+        synchronized (mLock) {
+            if (ACTION_BIND_IMS_SERVICE.equals(intent.getAction())) {
+                if (ImsUtils.VDBG) {
+                    Log.i(TAG, "onBind-Remote");
+                }
+                mIsTelephonyBound = true;
+                return super.onBind(intent);
             }
-            return super.onBind(intent);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "onBind-Local");
+            }
+            return mBinder;
         }
-        if (ImsUtils.VDBG) {
-            Log.i(TAG, "onBind-Local");
-        }
-        return mBinder;
     }
 }
diff --git a/tests/tests/telephony/current/permissions/Android.bp b/tests/tests/telephony/current/permissions/Android.bp
deleted file mode 100644
index f13b0e3..0000000
--- a/tests/tests/telephony/current/permissions/Android.bp
+++ /dev/null
@@ -1,40 +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 {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "CtsTelephonyTestCasesPermissionReadPhoneState",
-    defaults: ["cts_defaults"],
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    // Include both the 32 and 64 bit versions
-    compile_multilib: "both",
-    static_libs: [
-        "ctstestrunner-axt",
-        "compatibility-device-util-axt",
-    ],
-    srcs: [
-    "src/**/*.java",
-    ":cts-telephony-utils"
-    ],
-    sdk_version: "test_current",
-}
diff --git a/tests/tests/telephony/current/permissions/AndroidManifest.xml b/tests/tests/telephony/current/permissions/AndroidManifest.xml
deleted file mode 100644
index 8247dcd..0000000
--- a/tests/tests/telephony/current/permissions/AndroidManifest.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephony.cts.telephonypermission" android:targetSandboxVersion="2">
-
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-
-    <!-- Must be debuggable for compat shell commands to work on user builds -->
-    <application android:debuggable="true" />
-
-    <!--
-        The CTS stubs package cannot be used as the target application here,
-        since that requires many permissions to be set. Instead, specify this
-        package itself as the target and include any stub activities needed.
-
-        This test package uses the default InstrumentationTestRunner, because
-        the InstrumentationCtsTestRunner is only available in the stubs
-        package. That runner cannot be added to this package either, since it
-        relies on hidden APIs.
-    -->
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telephony.cts.telephonypermission"
-                     android:label="CTS tests of android.permission">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/telephony/current/permissions/AndroidTest.xml b/tests/tests/telephony/current/permissions/AndroidTest.xml
deleted file mode 100644
index b3cce9e..0000000
--- a/tests/tests/telephony/current/permissions/AndroidTest.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Telephony Permission test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-
-    <!-- Telephony permission tests do not use any permission not available to instant apps. -->
-    <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" />
-
-    <!-- Install main test suite apk -->
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsTelephonyTestCasesPermissionReadPhoneState.apk" />
-    </target_preparer>
-
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.telephony.cts.telephonypermission" />
-        <option name="runtime-hint" value="1m" />
-    </test>
-</configuration>
diff --git a/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java b/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
deleted file mode 100644
index 7d164d4..0000000
--- a/tests/tests/telephony/current/permissions/src/android/telephony/cts/telephonypermission/TelephonyManagerReadPhoneStatePermissionTest.java
+++ /dev/null
@@ -1,274 +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.telephony.cts.telephonypermission;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.platform.test.annotations.AppModeFull;
-import android.telecom.PhoneAccount;
-import android.telecom.TelecomManager;
-import android.telephony.SubscriptionManager;
-import android.telephony.TelephonyManager;
-import android.telephony.cts.TelephonyUtils;
-import android.telephony.emergency.EmergencyNumber;
-
-import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test TelephonyManager APIs with READ_PHONE_STATE Permission.
- */
-@RunWith(AndroidJUnit4.class)
-@AppModeFull(reason = "Cannot grant the runtime permission in instant app mode")
-public class TelephonyManagerReadPhoneStatePermissionTest {
-
-    private boolean mHasTelephony;
-    TelephonyManager mTelephonyManager = null;
-    TelecomManager mTelecomManager = null;
-
-    @Before
-    public void setUp() throws Exception {
-        mHasTelephony = getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY);
-        mTelephonyManager =
-                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        assertNotNull(mTelephonyManager);
-        mTelecomManager =
-                (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE);
-        assertNotNull(mTelecomManager);
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
-                TelephonyUtils.CTS_APP_PACKAGE,
-                TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
-    }
-
-    /**
-     * Verify that TelephonyManager APIs requiring READ_PHONE_STATE Permission must work.
-     * <p>
-     * Requires Permission:
-     * {@link android.Manifest.permission#READ_PHONE_STATE}.
-     *
-     * APIs list:
-     * getDeviceSoftwareVersion()
-     * getCarrierConfig()
-     * getNetworkType()
-     * getDataNetworkType()
-     * getVoiceNetworkType()
-     * getGroupIdLevel1()
-     * getLine1AlphaTag()
-     * getVoiceMailNumber()
-     * getVisualVoicemailPackageName()
-     * getVoiceMailAlphaTag()
-     * getForbiddenPlmns()
-     * isDataRoamingEnabled()
-     * getSubscriptionId(@NonNull PhoneAccountHandle phoneAccountHandle)
-     * getServiceState()
-     * getEmergencyNumberList()
-     * getEmergencyNumberList(@EmergencyServiceCategories int categories)
-     * getPreferredOpportunisticDataSubscription()
-     * isModemEnabledForSlot(int slotIndex)
-     * isMultiSimSupported()
-     * doesSwitchMultiSimConfigTriggerReboot()
-     * getCallState() (when compat fwk enables enforcement)
-     * getCallStateForSubscription() (when compat fwk enables enforcement)
-     */
-    @Test
-    public void testTelephonyManagersAPIsRequiringReadPhoneStatePermissions() throws Exception {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        try {
-            // We must ensure that compat fwk enables READ_PHONE_STATE enforcement
-            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
-                    TelephonyUtils.CTS_APP_PACKAGE,
-                    TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getCallState());
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getCallStateForSubscription());
-        } catch (SecurityException e) {
-            fail("TelephonyManager#getCallState and TelephonyManager#getCallStateForSubscription "
-                    + "must not throw a SecurityException because READ_PHONE_STATE permission is "
-                    + "granted and TelecomManager#ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION is "
-                    + "enabled.");
-        }
-
-        int subId = mTelephonyManager.getSubscriptionId();
-
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getNetworkType());
-        } catch (SecurityException e) {
-            fail("getNetworkType() must not throw a SecurityException with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getDeviceSoftwareVersion());
-        } catch (SecurityException e) {
-            fail("getDeviceSoftwareVersion() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getCarrierConfig());
-        } catch (SecurityException e) {
-            fail("getCarrierConfig() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getDataNetworkType());
-        } catch (SecurityException e) {
-            fail("getDataNetworkType() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getVoiceNetworkType());
-        } catch (SecurityException e) {
-            fail("getVoiceNetworkType() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getGroupIdLevel1());
-        } catch (SecurityException e) {
-            fail("getGroupIdLevel1() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getLine1AlphaTag());
-        } catch (SecurityException e) {
-            fail("getLine1AlphaTag() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getVoiceMailNumber());
-        } catch (SecurityException e) {
-            fail("getVoiceMailNumber() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getVisualVoicemailPackageName());
-        } catch (SecurityException e) {
-            fail("getVisualVoicemailPackageName() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getVoiceMailAlphaTag());
-        } catch (SecurityException e) {
-            fail("getVoiceMailAlphaTag() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getForbiddenPlmns());
-        } catch (SecurityException e) {
-            fail("getForbiddenPlmns() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.isDataRoamingEnabled());
-        } catch (SecurityException e) {
-            fail("isDataRoamingEnabled() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getSubscriptionId(
-                            mTelecomManager.getDefaultOutgoingPhoneAccount(
-                                    PhoneAccount.SCHEME_TEL)));
-        } catch (SecurityException e) {
-            fail("getSubscriptionId(phoneAccountHandle) must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getServiceState());
-        } catch (SecurityException e) {
-            fail("getServiceState() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getEmergencyNumberList());
-        } catch (SecurityException e) {
-            fail("getEmergencyNumberList() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getEmergencyNumberList(
-                            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
-        } catch (SecurityException e) {
-            fail("getEmergencyNumberList(EMERGENCY_SERVICE_CATEGORY_POLICE) must"
-                    + " not throw a SecurityException with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.getPreferredOpportunisticDataSubscription());
-        } catch (SecurityException e) {
-            fail("getPreferredOpportunisticDataSubscription() must not throw"
-                    + " a SecurityException with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.isModemEnabledForSlot(
-                            SubscriptionManager.getSlotIndex(subId)));
-        } catch (SecurityException e) {
-            fail("isModemEnabledForSlot(slotIndex) must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.isMultiSimSupported());
-        } catch (SecurityException e) {
-            fail("isMultiSimSupported() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-        try {
-            ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTelephonyManager, (tm) -> tm.doesSwitchMultiSimConfigTriggerReboot());
-        } catch (SecurityException e) {
-            fail("doesSwitchMultiSimConfigTriggerReboot() must not throw a SecurityException"
-                    + " with READ_PHONE_STATE" + e);
-        }
-    }
-
-    private static Context getContext() {
-        return InstrumentationRegistry.getContext();
-    }
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
index 1be3eb8..bb25a89 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/ApnSettingTest.java
@@ -42,6 +42,8 @@
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_EMERGENCY_STRING, ApnSetting.TYPE_EMERGENCY);
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_MCX_STRING, ApnSetting.TYPE_MCX);
         EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_XCAP_STRING, ApnSetting.TYPE_XCAP);
+        EXPECTED_STRING_TO_INT_MAP.put(ApnSetting.TYPE_ENTERPRISE_STRING,
+                ApnSetting.TYPE_ENTERPRISE);
 
         EXPECTED_INT_TO_STRING_MAP = new ArrayMap<>();
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_DEFAULT, ApnSetting.TYPE_DEFAULT_STRING);
@@ -56,6 +58,8 @@
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_EMERGENCY, ApnSetting.TYPE_EMERGENCY_STRING);
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_MCX, ApnSetting.TYPE_MCX_STRING);
         EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_XCAP, ApnSetting.TYPE_XCAP_STRING);
+        EXPECTED_INT_TO_STRING_MAP.put(ApnSetting.TYPE_ENTERPRISE,
+                ApnSetting.TYPE_ENTERPRISE_STRING);
     }
 
     @Test
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 e62adc5..542d687 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -35,7 +35,7 @@
 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.BroadcastReceiver;
@@ -70,7 +70,6 @@
     private CarrierConfigManager mConfigManager;
     private TelephonyManager mTelephonyManager;
     private SubscriptionManager mSubscriptionManager;
-    private PackageManager mPackageManager;
 
     // Use a long timeout to accommodate devices with lower amounts of memory, as it will take
     // longer for these devices to receive the broadcast (b/161963269). It is expected that all
@@ -80,6 +79,8 @@
 
     @Before
     public void setUp() throws Exception {
+        assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
         mTelephonyManager = (TelephonyManager)
                 getContext().getSystemService(Context.TELEPHONY_SERVICE);
         mConfigManager = (CarrierConfigManager)
@@ -87,7 +88,6 @@
         mSubscriptionManager =
                 (SubscriptionManager)
                         getContext().getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
-        mPackageManager = getContext().getPackageManager();
     }
 
     @After
@@ -174,6 +174,14 @@
             assertEquals("KEY_CARRIER_USSD_METHOD_INT doesn't match static default.",
                     config.getInt(CarrierConfigManager.KEY_CARRIER_USSD_METHOD_INT),
                             CarrierConfigManager.USSD_OVER_CS_PREFERRED);
+            assertEquals("KEY_USAGE_SETTING_INT doesn't match static default.",
+                    config.getInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT),
+                            SubscriptionManager.USAGE_SETTING_UNKNOWN);
+            assertEquals("KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL"
+                    + " doesn't match static default.",
+                    config.getBoolean(
+                      CarrierConfigManager.KEY_ENABLE_CROSS_SIM_CALLING_ON_OPPORTUNISTIC_DATA_BOOL),
+                      false);
         }
 
         // These key should return default values if not customized.
@@ -204,9 +212,6 @@
     @Test
     @AsbSecurityTest(cveBugId = 73136824)
     public void testRevokePermission() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         PersistableBundle config;
 
         try {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
index bae1f29..ea5d1ee 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierServiceTest.java
@@ -45,23 +45,27 @@
         }
     }
 
+    @Override
+    protected void runTest() throws Throwable {
+        if (!hasCellular()) {
+            return;
+        }
+        super.runTest();
+    }
+
     private static boolean hasCellular() {
         PackageManager packageManager = getInstrumentation().getContext().getPackageManager();
         TelephonyManager telephonyManager =
                 getInstrumentation().getContext().getSystemService(TelephonyManager.class);
-        return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)
                 && telephonyManager.getPhoneCount() > 0;
     }
 
     public void testNotifyCarrierNetworkChange_true() {
-        if (!mHasCellular) return;
-
         notifyCarrierNetworkChange(true);
     }
 
     public void testNotifyCarrierNetworkChange_false() {
-        if (!mHasCellular) return;
-
         notifyCarrierNetworkChange(false);
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java b/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
index 44a7ee5..af7a25c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/IRadioModemImpl.java
@@ -26,6 +26,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
 public class IRadioModemImpl extends IRadioModem.Stub {
     private static final String TAG = "MRMDM";
 
@@ -39,6 +41,8 @@
     private IRadioModemResponse mRadioModemResponse;
     private IRadioModemIndication mRadioModemIndication;
 
+    private int mForceRadioPowerError = -1;
+
     public IRadioModemImpl(MockModemService service) {
         Log.d(TAG, "Instantiated");
 
@@ -268,10 +272,17 @@
             boolean forEmergencyCall,
             boolean preferredForEmergencyCall) {
         Log.d(TAG, "setRadioPower");
+        RadioResponseInfo rsp = null;
 
         // TODO: cache value
 
-        RadioResponseInfo rsp = mService.makeSolRsp(serial);
+        // Check if the error response needs to be modified
+        if (mForceRadioPowerError != -1) {
+            rsp = mService.makeSolRsp(serial, mForceRadioPowerError);
+        } else {
+            rsp = mService.makeSolRsp(serial);
+        }
+
         try {
             mRadioModemResponse.setRadioPowerResponse(rsp);
         } catch (RemoteException ex) {
@@ -279,12 +290,14 @@
         }
 
         // TODO: The below should be handled by Helper function
-        if (powerOn) {
-            radioStateChanged(RadioState.ON);
-            mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_ON);
-        } else {
-            radioStateChanged(RadioState.OFF);
-            mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_OFF);
+        if (rsp.error == RadioError.NONE) {
+            if (powerOn) {
+                radioStateChanged(RadioState.ON);
+                mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_ON);
+            } else {
+                radioStateChanged(RadioState.OFF);
+                mService.countDownLatch(MockModemService.LATCH_MOCK_MODEM_RADIO_POWR_OFF);
+            }
         }
     }
 
@@ -347,4 +360,14 @@
             Log.e(TAG, "null mRadioModemIndication");
         }
     }
-}
+
+    public void forceErrorResponse(int requestId, int error) {
+        switch (requestId) {
+            case RIL_REQUEST_RADIO_POWER:
+                mForceRadioPowerError = error;
+                break;
+            default:
+                break;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
new file mode 100644
index 0000000..53b1016
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/InCallServiceStateValidator.java
@@ -0,0 +1,155 @@
+/*
+ * 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 android.content.Intent;
+import android.telecom.Call;
+import android.telecom.InCallService;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class InCallServiceStateValidator  extends InCallService {
+
+    private static final String LOG_TAG = "InCallServiceStateValidator";
+    private static final Object sLock = new Object();
+    private static InCallServiceCallbacks sCallbacks;
+    private static CountDownLatch sLatch  = new CountDownLatch(1);
+
+    private final List<Call> mCalls = Collections.synchronizedList(new ArrayList<>());
+    private final Object mLock = new Object();
+    private boolean mIsServiceBound = false;
+
+    public static class InCallServiceCallbacks {
+        private InCallServiceStateValidator mService = null;
+        public Semaphore lock = new Semaphore(0);
+
+        public void onCallAdded(Call call, int numCalls) {};
+        public void onCallRemoved(Call call, int numCalls) {};
+        public void onCallStateChanged(Call call, int state) {};
+
+        public InCallServiceStateValidator getService() {
+            if (mService == null) {
+                try {
+                    sLatch.await(3000, TimeUnit.MILLISECONDS);
+                } catch (InterruptedException e) {
+                    Log.d(LOG_TAG, "InterruptedException");
+                }
+                sLatch  = new CountDownLatch(1);
+            }
+            return mService;
+        }
+
+        public void setService(InCallServiceStateValidator service) {
+            mService = service;
+        }
+
+        public void resetLock() {
+            lock = new Semaphore(0);
+        }
+    }
+
+    public static void setCallbacks(InCallServiceCallbacks callbacks) {
+        synchronized (sLock) {
+            sCallbacks = callbacks;
+        }
+    }
+
+    private InCallServiceCallbacks getCallbacks() {
+        synchronized (sLock) {
+            if (sCallbacks != null) {
+                sCallbacks.setService(this);
+            }
+            return sCallbacks;
+        }
+    }
+
+    /**
+     * Note that the super implementations of the callback methods are all no-ops, but we call
+     * them anyway to make sure that the CTS coverage tool detects that we are testing them.
+     */
+    private Call.Callback mCallCallback = new Call.Callback() {
+        @Override
+        public void onStateChanged(Call call, int state) {
+            super.onStateChanged(call, state);
+            if (getCallbacks() != null) {
+                getCallbacks().onCallStateChanged(call, state);
+            }
+        }
+    };
+
+    @Override
+    public android.os.IBinder onBind(android.content.Intent intent) {
+        if (getCallbacks() != null) {
+            getCallbacks().setService(this);
+        }
+        mIsServiceBound = true;
+        sLatch.countDown();
+        return super.onBind(intent);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mIsServiceBound = false;
+        mCalls.clear();
+        return super.onUnbind(intent);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        super.onCallAdded(call);
+        if (!mCalls.contains(call)) {
+            mCalls.add(call);
+            call.registerCallback(mCallCallback);
+        }
+
+        if (getCallbacks() != null) {
+            getCallbacks().onCallAdded(call, mCalls.size());
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        super.onCallRemoved(call);
+        mCalls.remove(call);
+
+        if (getCallbacks() != null) {
+            getCallbacks().onCallRemoved(call, mCalls.size());
+        }
+        call.unregisterCallback(mCallCallback);
+    }
+
+    /**
+    * @return the number of calls currently added to the {@code InCallService}.
+    */
+    public int getCallCount() {
+        return mCalls.size();
+    }
+
+    public boolean isServiceBound() {
+        return mIsServiceBound;
+    }
+
+    public boolean isServiceUnBound() {
+        return !mIsServiceBound;
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
index 57b5a84..fe9cf2c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MmsTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Activity;
 import android.app.PendingIntent;
@@ -94,17 +95,18 @@
     private Random mRandom;
     private SentReceiver mSentReceiver;
     private TelephonyManager mTelephonyManager;
-    private PackageManager mPackageManager;
 
     private static class SentReceiver extends BroadcastReceiver {
         private final Object mLock;
         private boolean mSuccess;
         private boolean mDone;
+        private int mExpectedErrorResultCode;
 
-        public SentReceiver() {
+        SentReceiver(int expectedErrorResultCode) {
             mLock = new Object();
             mSuccess = false;
             mDone = false;
+            mExpectedErrorResultCode = expectedErrorResultCode;
         }
 
         @Override
@@ -135,6 +137,9 @@
                 }
             } else {
                 Log.e(TAG, "Failure result=" + resultCode);
+                if (resultCode == mExpectedErrorResultCode) {
+                    mSuccess = true;
+                }
                 if (resultCode == SmsManager.MMS_ERROR_HTTP_FAILURE) {
                     final int httpError = intent.getIntExtra(SmsManager.EXTRA_MMS_HTTP_STATUS, 0);
                     Log.e(TAG, "HTTP failure=" + httpError);
@@ -161,7 +166,6 @@
                 Log.i(TAG, "Wait for sent: done=" + mDone + ", success=" + mSuccess);
                 return mDone && mSuccess;
             }
-
         }
     }
 
@@ -170,22 +174,30 @@
         mRandom = new Random();
         mTelephonyManager =
                 (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        mPackageManager = getContext().getPackageManager();
+        assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING));
     }
 
     @Test
     public void testSendMmsMessage() {
-        sendMmsMessage(0L /* messageId */);
+        sendMmsMessage(0L /* messageId */, Activity.RESULT_OK, SmsManager.getDefault());
+    }
+
+    @Test
+    public void testSendMmsMessageWithInactiveSubscriptionId() {
+        int inactiveSubId = 127;
+        sendMmsMessage(0L /* messageId */, SmsManager.MMS_ERROR_INACTIVE_SUBSCRIPTION,
+                SmsManager.getSmsManagerForSubscriptionId(inactiveSubId));
     }
 
     @Test
     public void testSendMmsMessageWithMessageId() {
-        sendMmsMessage(MESSAGE_ID);
+        sendMmsMessage(MESSAGE_ID, Activity.RESULT_OK, SmsManager.getDefault());
     }
 
-    private void sendMmsMessage(long messageId) {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-             || !doesSupportMMS()) {
+    private void sendMmsMessage(long messageId, int expectedErrorResultCode,
+            SmsManager smsManager) {
+        if (!doesSupportMMS()) {
             Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
             return;
         }
@@ -194,7 +206,7 @@
 
         final Context context = getContext();
         // Register sent receiver
-        mSentReceiver = new SentReceiver();
+        mSentReceiver = new SentReceiver(expectedErrorResultCode);
         context.registerReceiver(mSentReceiver, new IntentFilter(ACTION_MMS_SENT));
         // Create local provider file for sending PDU
         final String fileName = "send." + String.valueOf(Math.abs(mRandom.nextLong())) + ".dat";
@@ -213,14 +225,15 @@
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 context, 0, new Intent(ACTION_MMS_SENT), PendingIntent.FLAG_MUTABLE);
         if (messageId == 0L) {
-            SmsManager.getDefault().sendMultimediaMessage(context,
+            smsManager.sendMultimediaMessage(context,
                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent);
         } else {
-            SmsManager.getDefault().sendMultimediaMessage(context,
+            smsManager.sendMultimediaMessage(context,
                     contentUri, null/*locationUrl*/, null/*configOverrides*/, pendingIntent,
                     messageId);
         }
         assertTrue(mSentReceiver.waitForSuccess(SENT_TIMEOUT));
+        assertTrue(mSentReceiver.getResultCode() == expectedErrorResultCode);
         sendFile.delete();
     }
 
@@ -347,8 +360,7 @@
     }
 
     private void downloadMultimediaMessage(long messageId) {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || !doesSupportMMS()) {
+        if (!doesSupportMMS()) {
             Log.i(TAG, "testSendMmsMessage skipped: no telephony available or MMS not supported");
             return;
         }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java b/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
index 4b09895..543469a 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/MockModemService.java
@@ -29,6 +29,8 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -310,4 +312,24 @@
         sIRadioSimImpl.setSimPresent(slotId);
         sIRadioConfigImpl.setSimPresent(slotId);
     }
+
+    /**
+     * Change the response error per specific RIL request
+     *
+     * @param requestId the request/response message ID
+     * @param error RIL_Errno and -1 means to disable the modifed mechanism,
+     *  back to original mock modem behavior
+     */
+    public void forceErrorResponse(int requestId, int error) {
+        Log.d(TAG, "setReturnResponseError for request:" + requestId + " ,error:" + error);
+
+        switch (requestId) {
+            case RIL_REQUEST_RADIO_POWER:
+                sIRadioModemImpl.forceErrorResponse(requestId, error);
+                break;
+            default:
+                Log.e(TAG, "request:" + requestId + " not support to change the response error");
+                break;
+        }
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 8a13736..5ee3d44 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -112,7 +112,6 @@
     private static final String FINANCIAL_SMS_APP = "android.telephony.cts.financialsms";
 
     private TelephonyManager mTelephonyManager;
-    private PackageManager mPackageManager;
     private String mDestAddr;
     private String mText;
     private SmsBroadcastReceiver mSendReceiver;
@@ -139,24 +138,19 @@
 
     @Before
     public void setUp() throws Exception {
-        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+        assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING));
 
         mContext = getContext();
         mTelephonyManager =
             (TelephonyManager) getContext().getSystemService(
                     Context.TELEPHONY_SERVICE);
-        mPackageManager = mContext.getPackageManager();
         mDestAddr = mTelephonyManager.getLine1Number();
         mText = "This is a test message";
 
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            mDeliveryReportSupported = false;
-        } else {
-            // exclude the networks that don't support SMS delivery report
-            String mccmnc = mTelephonyManager.getSimOperator();
-            mDeliveryReportSupported = !(CarrierCapability.NO_DELIVERY_REPORTS.contains(mccmnc));
-        }
+        // exclude the networks that don't support SMS delivery report
+        String mccmnc = mTelephonyManager.getSimOperator();
+        mDeliveryReportSupported = !(CarrierCapability.NO_DELIVERY_REPORTS.contains(mccmnc));
 
         // register receivers
         mSendIntent = new Intent(SMS_SEND_ACTION);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index 615c2a8..c7065b7 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -26,6 +26,8 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -88,18 +90,18 @@
 
     @Before
     public void setUp() throws Exception {
+        assumeTrue(getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING));
+        mPackageManager = getContext().getPackageManager();
         mTelephonyManager =
             (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        mPackageManager = getContext().getPackageManager();
     }
 
     @Test
     public void testCreateFromPdu() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
-            // TODO: temp workaround, need to adjust test to use CDMA pdus
-            return;
-        }
+        // TODO: temp workaround, need to adjust test to use CDMA pdus
+        assumeFalse(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA));
 
         String pdu = "07916164260220F0040B914151245584F600006060605130308A04D4F29C0E";
         SmsMessage sms = SmsMessage.createFromPdu(hexStringToByteArray(pdu),
@@ -179,11 +181,9 @@
 
     @Test
     public void testCPHSVoiceMail() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
-            // TODO: temp workaround, need to adjust test to use CDMA pdus
-            return;
-        }
+        // TODO: temp workaround, need to adjust test to use CDMA pdus
+        assumeFalse(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA));
 
         // "set MWI flag"
         String pdu = "07912160130310F20404D0110041006060627171118A0120";
@@ -223,11 +223,9 @@
 
     @Test
     public void testGetUserData() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
-            // TODO: temp workaround, need to adjust test to use CDMA pdus
-            return;
-        }
+        // TODO: temp workaround, need to adjust test to use CDMA pdus
+        assumeFalse(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA));
 
         String pdu = "07914140279510F6440A8111110301003BF56080207130138A8C0B05040B8423F"
             + "000032A02010106276170706C69636174696F6E2F766E642E7761702E6D6D732D"
@@ -243,10 +241,6 @@
 
     @Test
     public void testGetSubmitPdu() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         SmsMessage.SubmitPdu smsPdu;
         String scAddress = null, destinationAddress = null;
         String message = null;
@@ -284,11 +278,9 @@
 
     @Test
     public void testEmailGateway() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_CDMA)) {
-            // TODO: temp workaround, need to adjust test to use CDMA pdus
-            return;
-        }
+        // TODO: temp workaround, need to adjust test to use CDMA pdus
+        assumeFalse(mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA));
 
         String pdu = "07914151551512f204038105f300007011103164638a28e6f71b50c687db" +
                          "7076d9357eb7412f7a794e07cdeb6275794c07bde8e5391d247e93f3";
@@ -317,10 +309,6 @@
 
     @Test
     public void testCalculateLength() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         int[] result = SmsMessage.calculateLength(LONG_TEXT_WITH_32BIT_CHARS, false);
         assertEquals(6, result.length);
         assertEquals(3, result[0]);
@@ -343,19 +331,12 @@
 
     @Test
     public void testCalculateLengthFlags() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         int[] result = SmsMessage.calculateLength(LONG_TEXT_WITH_FLAGS, false);
         assertEquals(2, result[0]);
     }
 
     @Test
     public void testGetSmsPdu() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         SmsMessage.SubmitPdu smsPdu;
         String scAddress = null;
         String destinationAddress = null;
@@ -391,9 +372,6 @@
 
     @Test
     public void testGetSubmitPduEncodedMessage() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         String destinationAddress = "18004664411";
         String message = "This is a test message";
 
@@ -433,9 +411,6 @@
 
     @Test
     public void testCreateFromNativeSmsSubmitPdu() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         // Short message with status RECEIVED_READ and size 0. See 3GPP2 C.S0023 3.4.27
         byte[] submitPdu = {1, 0};
         SmsMessage sms = SmsMessage.createFromNativeSmsSubmitPdu(submitPdu, true);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
index 32c4258..def4335 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsUsageMonitorShortCodeTest.java
@@ -17,25 +17,23 @@
 package android.telephony.cts;
 
 import static android.telephony.SmsManager.SMS_CATEGORY_FREE_SHORT_CODE;
-import static android.telephony.SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE;
 import static android.telephony.SmsManager.SMS_CATEGORY_NOT_SHORT_CODE;
 import static android.telephony.SmsManager.SMS_CATEGORY_POSSIBLE_PREMIUM_SHORT_CODE;
 import static android.telephony.SmsManager.SMS_CATEGORY_PREMIUM_SHORT_CODE;
+import static android.telephony.SmsManager.SMS_CATEGORY_STANDARD_SHORT_CODE;
 
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.telephony.SmsManager;
-import android.test.InstrumentationTestCase;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsManager;
 
 import androidx.test.annotation.UiThreadTest;
 
-import com.android.internal.telephony.SmsUsageMonitor;
-
 import org.junit.Before;
 import org.junit.Test;
 
@@ -44,7 +42,6 @@
  */
 public class SmsUsageMonitorShortCodeTest {
 
-    private PackageManager mPackageManager;
     private Context mContext;
 
     private static final class ShortCodeTest {
@@ -475,7 +472,8 @@
     @Before
     public void setUp() throws Exception {
         mContext = getInstrumentation().getTargetContext();
-        mPackageManager = mContext.getPackageManager();
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING));
     }
 
     private static int expectedReturnCode(String address) {
@@ -486,11 +484,6 @@
     @UiThreadTest
     @Test
     public void testSmsShortCodeDestination() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            // do not test if device does not support telephony (voice or sms)
-            return;
-        }
-
         for (ShortCodeTest test : sShortCodeTests) {
             // It is intended that a short code number in country A may be an emergency number
             // in country B. It is intended that the destination will be changed because of this
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java b/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java
deleted file mode 100644
index 51491d2..0000000
--- a/tests/tests/telephony/current/src/android/telephony/cts/StubInCallService.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.cts;
-
-import android.telecom.InCallService;
-
-/**
- * A in call service that does nothing except allowing CTS telephony to be set as a default dialer
- */
-public class StubInCallService extends InCallService {
-
-}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index 5335f96..041c133 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -30,9 +30,14 @@
 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.annotation.Nullable;
 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.content.res.Resources;
 import android.net.ConnectivityManager;
@@ -64,6 +69,7 @@
 import com.android.compatibility.common.util.TestThread;
 import com.android.internal.util.ArrayUtils;
 
+import org.junit.After;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -107,6 +113,51 @@
     private int mDefaultVoiceSubId;
     private String mPackageName;
     private SubscriptionManager mSm;
+    private SubscriptionManagerTest.CarrierConfigReceiver mReceiver;
+
+    private static class CarrierConfigReceiver extends BroadcastReceiver {
+        private CountDownLatch mLatch = new CountDownLatch(1);
+        private final int mSubId;
+
+        CarrierConfigReceiver(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+                        SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+                if (mSubId == subId) {
+                    mLatch.countDown();
+                }
+            }
+        }
+
+        void clearQueue() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        void waitForCarrierConfigChanged() throws Exception {
+            mLatch.await(5000, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private void overrideCarrierConfig(PersistableBundle bundle, int subId) throws Exception {
+        mReceiver = new CarrierConfigReceiver(subId);
+        IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
+        InstrumentationRegistry.getContext().registerReceiver(mReceiver, filter);
+        mReceiver.waitForCarrierConfigChanged();
+        mReceiver.clearQueue();
+
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                InstrumentationRegistry.getContext().getSystemService(CarrierConfigManager.class),
+                (cm) -> cm.overrideConfig(subId, bundle));
+        mReceiver.waitForCarrierConfigChanged();
+        InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
+        mReceiver = null;
+    }
 
     /**
      * Callback used in testRegisterNetworkCallback that allows caller to block on
@@ -155,12 +206,21 @@
 
     @Before
     public void setUp() throws Exception {
-        if (!isSupported()) return;
+        assumeTrue(isSupported());
 
         mSm = InstrumentationRegistry.getContext().getSystemService(SubscriptionManager.class);
         mSubId = SubscriptionManager.getDefaultDataSubscriptionId();
         mDefaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
         mPackageName = InstrumentationRegistry.getContext().getPackageName();
+
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mReceiver != null) {
+            InstrumentationRegistry.getContext().unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
     }
 
     /**
@@ -170,34 +230,25 @@
      */
     @Test
     public void testCorrectness() throws Exception {
-        if (!isSupported()) return;
-
         final boolean hasCellular = findCellularNetwork() != null;
-        if (isSupported() && !hasCellular) {
+        if (!hasCellular) {
             fail("Device claims to support " + PackageManager.FEATURE_TELEPHONY
                     + " but has no active cellular network, which is required for validation");
-        } else if (!isSupported() && hasCellular) {
-            fail("Device has active cellular network, but claims to not support "
-                    + PackageManager.FEATURE_TELEPHONY);
         }
 
-        if (isSupported()) {
-            if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                fail("Device must have a valid default data subId for validation");
-            }
+        if (mSubId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            fail("Device must have a valid default data subId for validation");
         }
     }
 
     @Test
     public void testGetActiveSubscriptionInfoCount() throws Exception {
-        if (!isSupported()) return;
         assertTrue(mSm.getActiveSubscriptionInfoCount() <=
                 mSm.getActiveSubscriptionInfoCountMax());
     }
 
     @Test
     public void testGetActiveSubscriptionInfoForIcc() throws Exception {
-        if (!isSupported()) return;
         SubscriptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getActiveSubscriptionInfo(mSubId));
         assertNotNull(ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
@@ -206,13 +257,11 @@
 
     @Test
     public void testIsActiveSubscriptionId() throws Exception {
-        if (!isSupported()) return;
         assertTrue(mSm.isActiveSubscriptionId(mSubId));
     }
 
     @Test
     public void testGetSubscriptionIds() throws Exception {
-        if (!isSupported()) return;
         int slotId = SubscriptionManager.getSlotIndex(mSubId);
         int[] subIds = mSm.getSubscriptionIds(slotId);
         assertNotNull(subIds);
@@ -221,7 +270,6 @@
 
     @Test
     public void testGetResourcesForSubId() {
-        if (!isSupported()) return;
         Resources r = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getResourcesForSubId(InstrumentationRegistry.getContext(), mSubId));
         // this is an old method which returns mcc/mnc as ints, so use the old SM.getMcc/Mnc methods
@@ -232,14 +280,11 @@
 
     @Test
     public void testIsUsableSubscriptionId() throws Exception {
-        if (!isSupported()) return;
         assertTrue(SubscriptionManager.isUsableSubscriptionId(mSubId));
     }
 
     @Test
     public void testActiveSubscriptions() throws Exception {
-        if (!isSupported()) return;
-
         List<SubscriptionInfo> subList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getActiveSubscriptionInfoList());
         int[] idList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
@@ -265,8 +310,6 @@
 
     @Test
     public void testSubscriptionPlans() throws Exception {
-        if (!isSupported()) return;
-
         // Make ourselves the owner
         setSubPlanOwner(mSubId, mPackageName);
 
@@ -295,8 +338,6 @@
 
     @Test
     public void testSubscriptionPlansOverrideCongested() throws Exception {
-        if (!isSupported()) return;
-
         final ConnectivityManager cm = InstrumentationRegistry.getContext()
                 .getSystemService(ConnectivityManager.class);
         final Network net = findCellularNetwork();
@@ -351,7 +392,7 @@
 
     @Test
     public void testSubscriptionInfoRecord() {
-        if (!isSupported() || !isAutomotive()) return;
+        if (!isAutomotive()) return;
 
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity();
@@ -380,8 +421,6 @@
 
     @Test
     public void testSetDefaultVoiceSubId() {
-        if (!isSupported()) return;
-
         int oldSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity();
@@ -399,8 +438,6 @@
 
     @Test
     public void testSubscriptionPlansOverrideUnmetered() throws Exception {
-        if (!isSupported()) return;
-
         final ConnectivityManager cm = InstrumentationRegistry.getContext()
                 .getSystemService(ConnectivityManager.class);
         final Network net = findCellularNetwork();
@@ -436,8 +473,6 @@
 
     @Test
     public void testSubscriptionPlansUnmetered() throws Exception {
-        if (!isSupported()) return;
-
         final ConnectivityManager cm = InstrumentationRegistry.getContext()
                 .getSystemService(ConnectivityManager.class);
         final Network net = findCellularNetwork();
@@ -485,8 +520,6 @@
 
     @Test
     public void testSubscriptionPlansInvalid() throws Exception {
-        if (!isSupported()) return;
-
         // Make ourselves the owner
         setSubPlanOwner(mSubId, mPackageName);
 
@@ -526,8 +559,6 @@
 
     @Test
     public void testSubscriptionPlansNetworkTypeValidation() throws Exception {
-        if (!isSupported()) return;
-
         // Make ourselves the owner
         setSubPlanOwner(mSubId, mPackageName);
 
@@ -584,8 +615,6 @@
 
     @Test
     public void testSubscriptionGrouping() throws Exception {
-        if (!isSupported()) return;
-
         // Set subscription group with current sub Id. This should fail
         // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
         List<Integer> subGroup = new ArrayList();
@@ -629,8 +658,6 @@
 
     @Test
     public void testSubscriptionGroupingWithPermission() throws Exception {
-        if (!isSupported()) return;
-
         // Set subscription group with current sub Id.
         List<Integer> subGroup = new ArrayList();
         subGroup.add(mSubId);
@@ -686,8 +713,6 @@
 
     @Test
     public void testAddSubscriptionIntoNewGroupWithPermission() throws Exception {
-        if (!isSupported()) return;
-
         // Set subscription group with current sub Id.
         List<Integer> subGroup = new ArrayList();
         subGroup.add(mSubId);
@@ -718,8 +743,6 @@
 
     @Test
     public void testSettingOpportunisticSubscription() throws Exception {
-        if (!isSupported()) return;
-
         // Set subscription to be opportunistic. This should fail
         // because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
         try {
@@ -735,8 +758,6 @@
 
     @Test
     public void testMccMncString() {
-        if (!isSupported()) return;
-
         SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
         String mcc = info.getMccString();
         String mnc = info.getMncString();
@@ -746,8 +767,6 @@
 
     @Test
     public void testSetUiccApplicationsEnabled() throws Exception {
-        if (!isSupported()) return;
-
         boolean canDisable = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.canDisablePhysicalSubscription());
         if (canDisable) {
@@ -825,8 +844,6 @@
 
     @Test
     public void testSubscriptionInfoCarrierId() {
-        if (!isSupported()) return;
-
         SubscriptionInfo info = mSm.getActiveSubscriptionInfo(mSubId);
         int carrierId = info.getCarrierId();
         assertTrue(carrierId >= TelephonyManager.UNKNOWN_CARRIER_ID);
@@ -834,8 +851,6 @@
 
     @Test
     public void testGetOpportunisticSubscriptions() throws Exception {
-        if (!isSupported()) return;
-
         List<SubscriptionInfo> infoList = mSm.getOpportunisticSubscriptions();
 
         for (SubscriptionInfo info : infoList) {
@@ -845,7 +860,6 @@
 
     @Test
     public void testGetEnabledSubscriptionId() {
-        if (!isSupported()) return;
         int slotId = SubscriptionManager.getSlotIndex(mSubId);
         if (!SubscriptionManager.isValidSlotIndex(slotId)) {
             fail("Invalid slot id " + slotId + " for subscription id " + mSubId);
@@ -857,7 +871,6 @@
 
     @Test
     public void testSetAndCheckSubscriptionEnabled() {
-        if (!isSupported()) return;
         boolean enabled = executeWithShellPermissionAndDefault(false, mSm,
                 (sm) -> sm.isSubscriptionEnabled(mSubId));
 
@@ -946,8 +959,6 @@
 
     @Test
     public void testGetActiveDataSubscriptionId() {
-        if (!isSupported()) return;
-
         int activeDataSubIdCurrent = executeWithShellPermissionAndDefault(
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID, mSm,
                 (sm) -> sm.getActiveDataSubscriptionId());
@@ -962,7 +973,6 @@
 
     @Test
     public void testSetPreferredDataSubscriptionId() {
-        if (!isSupported()) return;
         int preferredSubId = executeWithShellPermissionAndDefault(-1, mSm,
                 (sm) -> sm.getPreferredDataSubscriptionId());
         if (preferredSubId != SubscriptionManager.DEFAULT_SUBSCRIPTION_ID) {
@@ -984,8 +994,6 @@
 
     @Test
     public void testRestoreAllSimSpecificSettingsFromBackup() throws Exception {
-        if (!isSupported()) return;
-
         int activeDataSubId = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
                 (sm) -> sm.getActiveDataSubscriptionId());
         assertNotEquals(activeDataSubId, SubscriptionManager.INVALID_SUBSCRIPTION_ID);
@@ -1089,8 +1097,6 @@
 
     @Test
     public void testSetAndGetD2DStatusSharing() {
-        if (!isSupported()) return;
-
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
         int originalD2DStatusSharing = mSm.getDeviceToDeviceStatusSharingPreference(mSubId);
@@ -1107,8 +1113,6 @@
 
     @Test
     public void testSetAndGetD2DSharingContacts() {
-        if (!isSupported()) return;
-
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         uiAutomation.adoptShellPermissionIdentity(MODIFY_PHONE_STATE);
         List<Uri> originalD2DSharingContacts = mSm.getDeviceToDeviceStatusSharingContacts(mSubId);
@@ -1120,8 +1124,6 @@
 
     @Test
     public void tetsSetAndGetPhoneNumber() throws Exception {
-        if (!isSupported()) return;
-
         // The phone number may be anything depends on the state of SIM and device.
         // Simply call the getter and make sure no exception.
 
@@ -1205,6 +1207,48 @@
         }
     }
 
+    @Test
+    public void testCellularUsageSetting() throws Exception {
+        if (!isSupported()) return;
+
+        boolean isUsageSettingSupported = true;
+        int defaultUsageSetting = SubscriptionManager.USAGE_SETTING_DEFAULT;
+        int[] supportedUsageSettings;
+        final Context context = InstrumentationRegistry.getContext();
+
+        //  Load the resources to provide the device capability
+        try {
+            defaultUsageSetting = context.getResources().getInteger(
+                com.android.internal.R.integer.config_default_cellular_usage_setting);
+            supportedUsageSettings = context.getResources().getIntArray(
+                com.android.internal.R.array.config_supported_cellular_usage_settings);
+            // If usage settings are not supported, return the default setting, which is UNKNOWN.
+            if (supportedUsageSettings.length < 1) {
+                isUsageSettingSupported = false;
+                fail("Usage Setting resources empty");
+            }
+        } catch (Resources.NotFoundException nfe) {
+            fail("Usage Setting resources not found");
+            isUsageSettingSupported = false;
+        }
+
+        int[] settingsToTest = new int[] {
+                SubscriptionManager.USAGE_SETTING_DEFAULT,
+                defaultUsageSetting};
+
+        for (int setting : settingsToTest) {
+            PersistableBundle bundle = new PersistableBundle();
+            bundle.putInt(CarrierConfigManager.KEY_CELLULAR_USAGE_SETTING_INT, setting);
+            overrideCarrierConfig(bundle, mSubId);
+            SubscriptionInfo info = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+                    (sm) -> sm.getActiveSubscriptionInfo(mSubId));
+            assertEquals(
+                    isUsageSettingSupported ? setting :
+                            SubscriptionManager.USAGE_SETTING_UNKNOWN,
+                    info.getUsageSetting());
+        }
+    }
+
     @Nullable
     private PersistableBundle getBundleFromBackupData(byte[] data) {
         try (ByteArrayInputStream bis = new ByteArrayInputStream(data)) {
@@ -1214,13 +1258,6 @@
         }
     }
 
-    private void overrideCarrierConfig(PersistableBundle bundle, int subId) throws Exception {
-        CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getContext()
-                .getSystemService(CarrierConfigManager.class);
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
-                (m) -> m.overrideConfig(subId, bundle));
-    }
-
     private void setPreferredDataSubId(int subId) {
         final LinkedBlockingQueue<Integer> resultQueue = new LinkedBlockingQueue<>(1);
         Executor executor = (command)-> command.run();
@@ -1337,8 +1374,8 @@
     }
 
     private static boolean isSupported() {
-        return InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
+        return InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
     }
 
     private static boolean isAutomotive() {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
new file mode 100644
index 0000000..29ccbc4
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyFeatureFlagsTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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 androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests for telephony related feature flags defined in {@link android.content.pm.PackageManager}
+ */
+public final class TelephonyFeatureFlagsTest {
+
+    private PackageManager mPackageManager;
+
+    @Before
+    public void setUp() {
+        mPackageManager = getContext().getPackageManager();
+    }
+
+    @Test
+    public void testFeatureFlagsValidation() throws Exception {
+        boolean hasFeatureTelecom = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELECOM);
+        boolean hasFeatureTelephony = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        boolean hasFeatureCalling = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
+        boolean hasFeatureCarrierLock = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CARRIERLOCK);
+        boolean hasFeatureCdma = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CDMA);
+        boolean hasFeatureData = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_DATA);
+        boolean hasFeatureEuicc = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_EUICC);
+        boolean hasFeatureGsm = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_GSM);
+        boolean hasFeatureIms = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS);
+        boolean hasFeatureSingleReg = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
+        boolean hasFeatureMbms = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MBMS);
+        boolean hasFeatureMessaging = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_MESSAGING);
+        boolean hasFeatureRadio = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS);
+        boolean hasFeatureSubscription = mPackageManager.hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION);
+
+        if (hasFeatureCalling) {
+            assertTrue(hasFeatureTelecom && hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureCarrierLock) {
+            assertTrue(hasFeatureSubscription);
+        }
+
+        if (hasFeatureCdma) {
+            assertTrue(hasFeatureRadio);
+        }
+
+        if (hasFeatureData) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureEuicc) {
+            assertTrue(hasFeatureSubscription);
+        }
+
+        if (hasFeatureGsm) {
+            assertTrue(hasFeatureRadio);
+        }
+
+        if (hasFeatureIms) {
+            assertTrue(hasFeatureData);
+        }
+
+        if (hasFeatureSingleReg) {
+            assertTrue(hasFeatureIms);
+        }
+
+        if (hasFeatureMbms) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureMessaging) {
+            assertTrue(hasFeatureRadio && hasFeatureSubscription);
+        }
+
+        if (hasFeatureRadio) {
+            assertTrue(hasFeatureTelephony);
+        }
+
+        if (hasFeatureSubscription) {
+            assertTrue(hasFeatureTelephony);
+        }
+    }
+}
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 49eb546..927caf0 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -32,6 +32,7 @@
 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.Manifest.permission;
 import android.annotation.NonNull;
@@ -51,6 +52,7 @@
 import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Looper;
+import android.os.Parcel;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.SystemProperties;
@@ -121,6 +123,7 @@
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -334,8 +337,10 @@
     @Before
     public void setUp() throws Exception {
         mCm = getContext().getSystemService(ConnectivityManager.class);
-        mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
         mPackageManager = getContext().getPackageManager();
+        assumeTrue(mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
+        mSubscriptionManager = getContext().getSystemService(SubscriptionManager.class);
         mCarrierConfigManager = getContext().getSystemService(CarrierConfigManager.class);
         mSelfPackageName = getContext().getPackageName();
         mSelfCertHash = getCertHash(mSelfPackageName);
@@ -375,7 +380,6 @@
     }
 
     private void saveAllowedNetworkTypesForAllReasons() {
-        if (!hasCellular()) return;
         mIsAllowedNetworkTypeChanged = false;
         if (mAllowedNetworkTypesList == null) {
             mAllowedNetworkTypesList = new HashMap<>();
@@ -443,15 +447,14 @@
         }
     }
 
-    /** Checks whether the cellular stack should be running on this device. */
-    private boolean hasCellular() {
-        return mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                && mTelephonyManager.getPhoneCount() > 0;
+    /** Checks whether the telephony feature is supported. */
+    private boolean hasFeature(String feature) {
+        return mPackageManager.hasSystemFeature(feature);
     }
 
     @Test
     public void testHasCarrierPrivilegesViaCarrierConfigs() throws Exception {
-        if (!hasCellular()) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
         PersistableBundle carrierConfig = mCarrierConfigManager.getConfigForSubId(mTestSub);
 
         try {
@@ -503,10 +506,8 @@
 
     @Test
     public void testDevicePolicyApn() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
         // These methods aren't accessible to anything except system and phone by design, so we just
         // look for security exceptions here.
         try {
@@ -548,12 +549,6 @@
 
     @Test
     public void testListen() throws Throwable {
-        if (!InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
-            return;
-        }
-
         if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
             // TODO: temp workaround, need to adjust test to for CDMA
             return;
@@ -635,11 +630,6 @@
      */
     @Test
     public void testTelephonyManager() {
-        if (!InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
-            return;
-        }
         assertTrue(mTelephonyManager.getNetworkType() >= TelephonyManager.NETWORK_TYPE_UNKNOWN);
         assertTrue(mTelephonyManager.getPhoneType() >= TelephonyManager.PHONE_TYPE_NONE);
         assertTrue(mTelephonyManager.getSimState() >= TelephonyManager.SIM_STATE_UNKNOWN);
@@ -774,10 +764,8 @@
 
     @Test
     public void testGetCallForwarding() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
         List<Integer> callForwardingReasons = new ArrayList<>();
         callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
         callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -840,10 +828,8 @@
 
     @Test
     public void testSetCallForwarding() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
         List<Integer> callForwardingReasons = new ArrayList<>();
         callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
         callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
@@ -910,6 +896,7 @@
         validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_DISABLED);
         validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
         validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+        validCallWaitingStatuses.add(TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE);
 
         LinkedBlockingQueue<Integer> callWaitingStatusResult = new LinkedBlockingQueue<>(1);
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
@@ -921,13 +908,12 @@
 
     @Test
     public void testSetCallWaitingStatus() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
         Set<Integer> validCallWaitingErrors = new HashSet<Integer>();
         validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
         validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+        validCallWaitingErrors.add(TelephonyManager.CALL_WAITING_STATUS_FDN_CHECK_FAILURE);
         Executor executor = getContext().getMainExecutor();
         {
             LinkedBlockingQueue<Integer> callWaitingResult = new LinkedBlockingQueue<>(1);
@@ -958,11 +944,6 @@
 
     @Test
     public void testGetRadioHalVersion() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG,"skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
-
         Pair<Integer, Integer> version = mTelephonyManager.getRadioHalVersion();
 
         // The version must be valid, and the versions start with 1.0
@@ -972,10 +953,6 @@
 
     @Test
     public void testCreateForPhoneAccountHandle() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
         if (!mTelephonyManager.isVoiceCapable()) {
             Log.d(TAG, "Skipping test that requires config_voice_capable is true");
             return;
@@ -1006,10 +983,6 @@
 
     @Test
     public void testGetPhoneAccountHandle() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
         TelecomManager telecomManager = getContext().getSystemService(TelecomManager.class);
         PhoneAccountHandle defaultAccount = telecomManager
                 .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
@@ -1063,11 +1036,6 @@
      */
     @Test
     public void testGetMaxNumberOfSimultaneouslyActiveSims() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
-
         int maxNum = mTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims();
         assertTrue(maxNum >= 1);
     }
@@ -1245,30 +1213,26 @@
 
     @Test
     public void testGetNetworkCountryIso() {
-        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            String countryCode = mTelephonyManager.getNetworkCountryIso();
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
+        String countryCode = mTelephonyManager.getNetworkCountryIso();
+        assertTrue("Country code '" + countryCode + "' did not match "
+                + ISO_COUNTRY_CODE_PATTERN,
+                Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
+
+        for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
+            countryCode = mTelephonyManager.getNetworkCountryIso(i);
+
             assertTrue("Country code '" + countryCode + "' did not match "
-                            + ISO_COUNTRY_CODE_PATTERN,
+                    + ISO_COUNTRY_CODE_PATTERN + " for slot " + i,
                     Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
-
-            for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
-                countryCode = mTelephonyManager.getNetworkCountryIso(i);
-
-                assertTrue("Country code '" + countryCode + "' did not match "
-                                + ISO_COUNTRY_CODE_PATTERN + " for slot " + i,
-                        Pattern.matches(ISO_COUNTRY_CODE_PATTERN, countryCode));
-            }
-        } else {
-            // Non-telephony may still have the property defined if it has a SIM.
         }
     }
 
     @Test
     public void testSetSystemSelectionChannels() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>(1);
         final UiAutomation uiAutomation =
                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1310,6 +1274,8 @@
 
     @Test
     public void testGetSimCountryIso() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         String countryCode = mTelephonyManager.getSimCountryIso();
         if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             assertTrue("Country code '" + countryCode + "' did not match "
@@ -1322,11 +1288,6 @@
 
     @Test
     public void testResetSettings() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
-
         UserManager userManager = getContext().getSystemService(UserManager.class);
 
         boolean canChangeMobileNetworkSettings = userManager != null
@@ -1386,6 +1347,8 @@
 
     @Test
     public void testGetServiceState() throws InterruptedException {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
             return;
@@ -1457,6 +1420,8 @@
 
     @Test
     public void testGetServiceStateForInactiveSub() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
             return;
@@ -1479,6 +1444,8 @@
     @Test
     @CddTest(requirement = "7.4.1/C-4-1")
     public void testIWlanServiceState() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
             return;
@@ -1513,10 +1480,6 @@
 
     @Test
     public void testGetPhoneCapabilityAndVerify() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG,"skipping test that requires Telephony");
-            return;
-        }
         boolean is5gStandalone = getContext().getResources().getBoolean(
                 Resources.getSystem().getIdentifier("config_telephony5gStandalone", "bool",
                         "android"));
@@ -1625,10 +1588,6 @@
      */
     @Test
     public void testGetImeiForSlot() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         for (int i = 0; i < mTelephonyManager.getPhoneCount(); i++) {
             // The compiler error 'local variables referenced from a lambda expression must be final
             // or effectively final' is reported when using i, so assign it to a final variable.
@@ -1653,9 +1612,7 @@
      */
     @Test
     public void testGetRadioPowerState() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Also verify that no exception is thrown.
         assertThat(mTelephonyManager.getRadioPowerState()).isEqualTo(
@@ -1668,9 +1625,8 @@
      */
     @Test
     public void testSetCarrierDataEnabled() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
         // Also verify that no exception is thrown.
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                 (tm) -> tm.setCarrierDataEnabled(false));
@@ -1684,9 +1640,8 @@
      */
     @Test
     public void testRebootRadio() throws Throwable {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         TestThread t = new TestThread(new Runnable() {
             public void run() {
                 Looper.prepare();
@@ -1782,9 +1737,8 @@
      */
     @Test
     public void testGetAidForAppType() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getAidForAppType(TelephonyManager.APPTYPE_SIM));
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -1802,9 +1756,8 @@
      */
     @Test
     public void testGetIsimDomain() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getIsimDomain());
     }
@@ -1816,9 +1769,8 @@
     @Ignore("API moved back to @hide for Android R.")
     @Test
     public void testGetIsimImpu() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 TelephonyManager::getIsimImpu);
         // Try without the correct permissions and ensure it fails.
@@ -1836,9 +1788,8 @@
      */
     @Test
     public void testNetworkRegistrationInfoRegisteredPlmn() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         // get NetworkRegistration object
         ServiceState ss = mTelephonyManager.getServiceState();
         assertNotNull(ss);
@@ -1870,9 +1821,8 @@
      */
     @Test
     public void testNetworkRegistrationInfoIsRoaming() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         // get NetworkRegistration object
         NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
                 .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1888,9 +1838,8 @@
      */
     @Test
     public void testNetworkRegistrationInfoGetRoamingType() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         // get NetworkRegistration object for voice
         NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
                 .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1914,9 +1863,8 @@
      */
     @Test
     public void testNetworkRegistationStateGetAccessNetworkTechnology() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         // get NetworkRegistration object for voice
         NetworkRegistrationInfo nwReg = mTelephonyManager.getServiceState()
                 .getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_CS,
@@ -1938,6 +1886,8 @@
      */
     @Test
     public void testGetMeid() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
+
         String meid = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getMeid());
 
@@ -1953,9 +1903,7 @@
      */
     @Test
     public void testGetMeidForSlot() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
 
         SubscriptionManager sm = getContext().getSystemService(SubscriptionManager.class);
         List<SubscriptionInfo> subInfos = sm.getActiveSubscriptionInfoList();
@@ -1991,10 +1939,6 @@
      */
     @Test
     public void testSendDialerSpecialCode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
         try {
             mTelephonyManager.sendDialerSpecialCode("4636");
             fail("Expected SecurityException. App does not have carrier privileges or is not the "
@@ -2008,9 +1952,8 @@
      */
     @Test
     public void testGetForbiddenPlmns() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         String[] plmns = mTelephonyManager.getForbiddenPlmns();
 
         int phoneType = mTelephonyManager.getPhoneType();
@@ -2039,9 +1982,8 @@
      */
     @Test
     public void testSetForbiddenPlmns() {
-        if (!supportSetFplmn()) {
-            return;
-        }
+        assumeTrue(supportSetFplmn());
+
         String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
         try {
             int numFplmnsSet = ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2063,9 +2005,8 @@
      */
     @Test
     public void testSetForbiddenPlmnsTruncate() {
-        if (!supportSetFplmn()) {
-            return;
-        }
+        assumeTrue(supportSetFplmn());
+
         String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
         try {
             List<String> targetFplmns = new ArrayList<>();
@@ -2096,9 +2037,8 @@
      */
     @Test
     public void testSetForbiddenPlmnsDelete() {
-        if (!supportSetFplmn()) {
-            return;
-        }
+        assumeTrue(supportSetFplmn());
+
         String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
         try {
             // Support test for empty SIM
@@ -2131,9 +2071,8 @@
      */
     @Test
     public void testSetForbiddenPlmnsVoid() {
-        if (!supportSetFplmn()) {
-            return;
-        }
+        assumeTrue(supportSetFplmn());
+
         String[] originalFplmns = mTelephonyManager.getForbiddenPlmns();
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2149,9 +2088,7 @@
 
     @Test
     public void testGetEquivalentHomePlmns() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
 
         List<String> plmns = mTelephonyManager.getEquivalentHomePlmns();
 
@@ -2175,9 +2112,8 @@
      */
     @Test
     public void testGetManualNetworkSelectionPlmnNonPersisted() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
@@ -2199,9 +2135,8 @@
      */
     @Test
     public void testGetManualNetworkSelectionPlmnPersisted() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         try {
@@ -2223,6 +2158,8 @@
      */
     @Test
     public void testGetCardIdForDefaultEuicc() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_EUICC));
+
         int cardId = mTelephonyManager.getCardIdForDefaultEuicc();
         assertTrue("Card ID for default EUICC is not a valid value",
                 cardId == TelephonyManager.UNSUPPORTED_CARD_ID
@@ -2235,10 +2172,8 @@
      */
     @Test
     public void testGetUiccCardsInfoException() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             // Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
             List<UiccCardInfo> infos = mTelephonyManager.getUiccCardsInfo();
@@ -2252,10 +2187,8 @@
      */
     @Test
     public void testGetUiccCardsInfo() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
         List<UiccCardInfo> infos =
                 ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2282,9 +2215,7 @@
      */
     @Test
     public void testGetNetworkSelectionMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
@@ -2304,10 +2235,8 @@
      */
     @Test
     public void testSetNetworkSelectionModeAutomatic() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         try {
             mTelephonyManager.setNetworkSelectionModeAutomatic();
             fail("Expected SecurityException. App does not have carrier privileges.");
@@ -2322,10 +2251,8 @@
      */
     @Test
     public void testSetNetworkSelectionModeManual() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         try {
             mTelephonyManager.setNetworkSelectionModeManual(
                     "" /* operatorNumeric */, false /* persistSelection */);
@@ -2339,10 +2266,8 @@
      */
     @Test
     public void testIsManualNetworkSelectionAllowed() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         if (mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_GSM) return;
 
         assertTrue(ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2420,8 +2345,52 @@
         assertEquals(false, cq.isRtpInactivityDetected());
         assertEquals(false, cq.isIncomingSilenceDetectedAtCallSetup());
         assertEquals(false, cq.isOutgoingSilenceDetectedAtCallSetup());
+        assertEquals(0, cq.getNumVoiceFrames());
+        assertEquals(0, cq.getNumNoDataFrames());
+        assertEquals(0, cq.getNumDroppedRtpPackets());
+        assertEquals(0, cq.getMinPlayoutDelayMillis());
+        assertEquals(0, cq.getMaxPlayoutDelayMillis());
+        assertEquals(0, cq.getNumRtpSidPacketsRx());
+        assertEquals(0, cq.getNumRtpDuplicatePackets());
     }
 
+    /**
+     * Validate CallQuality Parcel
+     */
+    @Test
+    public void testCallQualityParcel() {
+        CallQuality cq = new CallQuality.Builder()
+                .setDownlinkCallQualityLevel(CallQuality.CALL_QUALITY_NOT_AVAILABLE)
+                .setUplinkCallQualityLevel(CallQuality.CALL_QUALITY_NOT_AVAILABLE)
+                .setCallDuration(20000)
+                .setNumRtpPacketsTransmitted(550)
+                .setNumRtpPacketsReceived(450)
+                .setNumRtpPacketsTransmittedLost(4)
+                .setNumRtpPacketsNotReceived(6)
+                .setAverageRelativeJitter(20)
+                .setMaxRelativeJitter(30)
+                .setAverageRoundTripTime(150)
+                .setCodecType(0)
+                .setRtpInactivityDetected(false)
+                .setIncomingSilenceDetectedAtCallSetup(false)
+                .setOutgoingSilenceDetectedAtCallSetup(false)
+                .setNumVoiceFrames(300)
+                .setNumNoDataFrames(300)
+                .setNumDroppedRtpPackets(5)
+                .setMinPlayoutDelayMillis(500)
+                .setMaxPlayoutDelayMillis(1000)
+                .setNumRtpSidPacketsRx(300)
+                .setNumRtpDuplicatePackets(0)
+                .build();
+
+        Parcel stateParcel = Parcel.obtain();
+        cq.writeToParcel(stateParcel, 0);
+        stateParcel.setDataPosition(0);
+
+        CallQuality parcelCq = CallQuality.CREATOR.createFromParcel(stateParcel);
+        assertThat(cq).isEqualTo(parcelCq);
+
+    }
 
     // Reference: packages/services/Telephony/ecc/input/eccdata.txt
     private static final Map<String, String> EMERGENCY_NUMBERS_FOR_COUNTRIES =
@@ -2444,9 +2413,8 @@
      */
     @Test
     public void testGetEmergencyNumberList() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
         Map<Integer, List<EmergencyNumber>> emergencyNumberList =
                 mTelephonyManager.getEmergencyNumberList();
 
@@ -2469,9 +2437,8 @@
      */
     @Test
     public void testGetEmergencyNumberListForCategories() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
+
         Map<Integer, List<EmergencyNumber>> emergencyNumberList =
                 mTelephonyManager.getEmergencyNumberList(
                         EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE);
@@ -2500,9 +2467,7 @@
      */
     @Test
     public void testIsEmergencyNumber() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
 
         for (Map.Entry<String, String> entry : EMERGENCY_NUMBERS_FOR_COUNTRIES.entrySet()) {
             if (mTelephonyManager.getNetworkCountryIso().equals(entry.getKey())) {
@@ -2516,9 +2481,7 @@
      */
     @Test
     public void testIsPotentialEmergencyNumber() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
 
         String countryIso = mTelephonyManager.getNetworkCountryIso();
         String potentialEmergencyAddress = "91112345";
@@ -2539,14 +2502,9 @@
      */
     @Test
     public void testSetGetCallComposerStatus() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CALLING));
 
-        boolean hasImsFeature = mPackageManager.hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY_IMS);
-
-        if (hasImsFeature) {
+        if (hasFeature(PackageManager.FEATURE_TELEPHONY_IMS)) {
             ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                     tm -> tm.setCallComposerStatus(TelephonyManager.CALL_COMPOSER_STATUS_OFF));
             int status = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -2578,9 +2536,8 @@
      */
     @Test
     public void testGetRadioAccessFamily() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         long raf = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getSupportedRadioAccessFamily());
         assertThat(raf).isNotEqualTo(TelephonyManager.NETWORK_TYPE_BITMASK_UNKNOWN);
@@ -2601,6 +2558,8 @@
      */
     @Test
     public void testPreferredOpportunisticDataSubscription() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
         int randomSubId = 1;
         int activeSubscriptionInfoCount = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mSubscriptionManager, (tm) -> tm.getActiveSubscriptionInfoCount());
@@ -2727,15 +2686,14 @@
      */
     @Test
     public void testUpdateAvailableNetworks() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         int randomSubId = 1;
         int activeSubscriptionInfoCount = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mSubscriptionManager, (tm) -> tm.getActiveSubscriptionInfoCount());
         boolean isOpportunisticNetworkEnabled = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isOpportunisticNetworkEnabled());
 
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         if (!isOpportunisticNetworkEnabled) {
             return;
         }
@@ -2792,9 +2750,8 @@
 
     @Test
     public void testSwitchMultiSimConfig() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             mTelephonyManager.switchMultiSimConfig(mTelephonyManager.getActiveModemCount());
             fail("TelephonyManager#switchMultiSimConfig should require the MODIFY_PHONE_STATE"
@@ -2815,9 +2772,8 @@
 
     @Test
     public void testIccOpenLogicalChannelBySlot() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // just verify no crash
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -2828,25 +2784,73 @@
     }
 
     @Test
-    public void testIccCloseLogicalChannelBySlot() {
+    public void testIccOpenLogicalChannelBySlotAndPort() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
         // just verify no crash
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccOpenLogicalChannelByPort(0, 0, null, 0));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+        }
+    }
+
+    @Test
+    public void testIccCloseLogicalChannelBySlot() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        // just verify no crash
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
                     mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelBySlot(0, 0));
         } catch (IllegalArgumentException e) {
             // IllegalArgumentException is okay, just not SecurityException
         }
     }
-
     @Test
-    public void testIccTransmitApduLogicalChannelBySlot() {
+    public void testIccCloseLogicalChannelBySlotAndPort() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
-        int slotIndex = getValidSlotIndex();
+        int slotIndex = getValidSlotIndexAndPort().getKey();
+        int portIndex = getValidSlotIndexAndPort().getValue();
+        // just verify no crash
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(
+                            slotIndex, portIndex, 0));
+        } catch (IllegalArgumentException | IllegalStateException e) {
+            // IllegalArgumentException and IllegalStateException is okay, just not
+            // SecurityException
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccCloseLogicalChannelByPort: SecurityException not expected");
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(slotIndex, -1, 0));
+            fail("Expected IllegalArgumentException, invalid PortIndex");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager, (tm) -> tm.iccCloseLogicalChannelByPort(
+                            slotIndex, portIndex, -1));
+            fail("Expected IllegalArgumentException, invalid channel");
+        } catch (IllegalArgumentException e) {
+            // IllegalArgumentException is expected
+        }
+    }
+
+    @Test
+    public void testIccTransmitApduLogicalChannelBySlot() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        int slotIndex = getValidSlotIndexAndPort().getKey();
         String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelBySlot(
                         slotIndex,
@@ -2861,12 +2865,36 @@
     }
 
     @Test
-    public void testIccTransmitApduBasicChannelBySlot() {
+    public void testIccTransmitApduLogicalChannelBySlotAndPort() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        int slotIndex = getValidSlotIndexAndPort().getKey();
+        int portIndex = getValidSlotIndexAndPort().getValue();
+        try {
+            String result = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccTransmitApduLogicalChannelByPort(
+                            slotIndex,
+                            portIndex /* portIndex */,
+                            0 /* channel */,
+                            0 /* cla */,
+                            0 /* instruction */,
+                            0 /* p1 */,
+                            0 /* p2 */,
+                            0 /* p3 */,
+                            null /* data */));
+            assertTrue(TextUtils.isEmpty(result));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccTransmitApduLogicalChannelByPort: SecurityException not expected");
+        }
+    }
+    @Test
+    public void testIccTransmitApduBasicChannelBySlot() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // just verify no crash
-        int slotIndex = getValidSlotIndex();
+        int slotIndex = getValidSlotIndexAndPort().getKey();
         try {
             ShellIdentityUtils.invokeMethodWithShellPermissions(
                     mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelBySlot(
@@ -2883,10 +2911,34 @@
     }
 
     @Test
-    public void testIsIccLockEnabled() {
+    public void testIccTransmitApduBasicChannelBySlotAndPort() {
         if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
             return;
         }
+        // just verify no crash
+        int slotIndex = getValidSlotIndexAndPort().getKey();
+        int portIndex = getValidSlotIndexAndPort().getValue();
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.iccTransmitApduBasicChannelByPort(
+                            slotIndex,
+                            portIndex /*portIndex */,
+                            0 /* cla */,
+                            0 /* instruction */,
+                            0 /* p1 */,
+                            0 /* p2 */,
+                            0 /* p3 */,
+                            null /* data */));
+        } catch (SecurityException e) {
+            // IllegalArgumentException is okay, just not SecurityException
+            fail("iccTransmitApduBasicChannelByPort: SecurityException not expected");
+        }
+    }
+
+    @Test
+    public void testIsIccLockEnabled() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // verify SecurityException
         try {
             mTelephonyManager.isIccLockEnabled();
@@ -2906,9 +2958,8 @@
 
     @Test
     public void testIsDataEnabledForApn() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
         // verify SecurityException
         try {
             mTelephonyManager.isDataEnabledForApn(ApnSetting.TYPE_MMS);
@@ -2928,9 +2979,8 @@
 
     @Test
     public void testIsTetheringApnRequired() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
+
         // verify SecurityException
         try {
             mTelephonyManager.isTetheringApnRequired();
@@ -2951,9 +3001,8 @@
 
     @Test
     public void testGetCarrierInfoForImsiEncryption() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // test without permission: verify SecurityException
         try {
             mTelephonyManager.getCarrierInfoForImsiEncryption(TelephonyManager.KEY_TYPE_EPDG);
@@ -3044,9 +3093,8 @@
 
     @Test
     public void testResetCarrierKeysForImsiEncryption() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         // test without permission: verify SecurityException
         try {
             mTelephonyManager.resetCarrierKeysForImsiEncryption();
@@ -3066,9 +3114,8 @@
 
     @Test
     public void testIsInEmergencySmsMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING));
+
         // test without permission: verify SecurityException
         try {
             mTelephonyManager.isInEmergencySmsMode();
@@ -3088,9 +3135,7 @@
 
     @Test
     public void testGetSubscriptionId() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
 
         TelephonyManager tm = mTelephonyManager.createForSubscriptionId(1);
         int subId = tm.getSubscriptionId();
@@ -3099,9 +3144,7 @@
 
     @Test
     public void testSetAllowedNetworkTypes() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // test without permission: verify SecurityException
         long allowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR;
@@ -3131,9 +3174,7 @@
 
     @Test
     public void testDisAllowedNetworkTypes() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         long allowedNetworkTypes = -1 & (~TelephonyManager.NETWORK_TYPE_BITMASK_NR);
         long networkTypeBitmask = TelephonyManager.NETWORK_TYPE_BITMASK_NR
@@ -3174,9 +3215,7 @@
 
     @Test
     public void testSetAllowedNetworkTypesForReason() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // test without permission: verify SecurityException
         long allowedNetworkTypes = TelephonyManager.NETWORK_TYPE_BITMASK_NR;
@@ -3211,9 +3250,7 @@
 
     @Test
     public void testSetAllowedNetworkTypesForReason_moreReason() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // test without permission: verify SecurityException
         long allowedNetworkTypes1 = TelephonyManager.NETWORK_TYPE_BITMASK_NR
@@ -3284,9 +3321,7 @@
 
     @Test
     public void testIsApplicationOnUicc() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
 
         // Expect a security exception without permission.
         try {
@@ -3310,10 +3345,6 @@
 
     @Test
     public void testRequestModemActivityInfo() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
         try {
@@ -3353,10 +3384,6 @@
 
     @Test
     public void testGetSupportedModemCount() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         int supportedModemCount = mTelephonyManager.getSupportedModemCount();
         int activeModemCount = mTelephonyManager.getActiveModemCount();
         assertTrue(activeModemCount >= 0);
@@ -3396,10 +3423,6 @@
 
     @Test
     public void testIsModemEnabledForSlot() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         int activeModemCount = mTelephonyManager.getActiveModemCount();
         for (int i = 0; i < activeModemCount; i++) {
             // Call isModemEnabledForSlot for each slot and verify no crash.
@@ -3409,12 +3432,8 @@
 
     @Test
     public void testOpportunisticNetworkState() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS)
+                && !mPackageManager.hasSystemFeature(PackageManager.FEATURE_WATCH));
 
         boolean isEnabled = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 tm -> tm.isOpportunisticNetworkEnabled());
@@ -3432,9 +3451,8 @@
 
     @Test
     public void testGetSimApplicationState() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         int simApplicationState = mTelephonyManager.getSimApplicationState();
         assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
                 TelephonyManager.SIM_STATE_PIN_REQUIRED,
@@ -3460,9 +3478,8 @@
 
     @Test
     public void testGetSimCardState() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         int simCardState = mTelephonyManager.getSimCardState();
         assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
                 TelephonyManager.SIM_STATE_ABSENT,
@@ -3470,6 +3487,27 @@
                 TelephonyManager.SIM_STATE_CARD_RESTRICTED,
                 TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
     }
+    @Test
+    public void getSimCardStateTest() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+        List<UiccCardInfo> cardsInfo = mTelephonyManager.getUiccCardsInfo();
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        for (UiccCardInfo cardInfo : cardsInfo) {
+            for (UiccPortInfo portInfo : cardInfo.getPorts()) {
+                int simCardState = mTelephonyManager.getSimCardState(cardInfo
+                        .getPhysicalSlotIndex(), portInfo.getPortIndex());
+                assertTrue(Arrays.asList(TelephonyManager.SIM_STATE_UNKNOWN,
+                        TelephonyManager.SIM_STATE_ABSENT,
+                        TelephonyManager.SIM_STATE_CARD_IO_ERROR,
+                        TelephonyManager.SIM_STATE_CARD_RESTRICTED,
+                        TelephonyManager.SIM_STATE_PRESENT).contains(simCardState));
+            }
+        }
+    }
 
     private boolean isDataEnabled() {
         return ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -3478,10 +3516,11 @@
 
     @Test
     public void testThermalDataEnable() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_THERMAL,
@@ -3513,10 +3552,11 @@
 
     @Test
     public void testPolicyDataEnable() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_POLICY,
@@ -3548,10 +3588,11 @@
 
     @Test
     public void testCarrierDataEnable() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
+        // Perform this test on default data subscription.
+        mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
+                .createForSubscriptionId(SubscriptionManager.getDefaultDataSubscriptionId());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
                 (tm) -> tm.setDataEnabledForReason(TelephonyManager.DATA_ENABLED_REASON_CARRIER,
@@ -3584,9 +3625,7 @@
 
     @Test
     public void testUserDataEnable() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mTelephonyManager,
@@ -3620,9 +3659,7 @@
 
     @Test
     public void testDataDuringVoiceCallPolicy() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
         ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
                 (tm) -> tm.isMobileDataPolicyEnabled(
@@ -3652,9 +3689,7 @@
 
     @Test
     public void testAlwaysAllowMmsDataPolicy() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_DATA));
 
         ShellIdentityUtils.ShellPermissionMethodHelper<Boolean, TelephonyManager> getPolicyHelper =
                 (tm) -> tm.isMobileDataPolicyEnabled(
@@ -3684,6 +3719,8 @@
 
     @Test
     public void testGetCdmaEnhancedRoamingIndicatorDisplayNumber() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA));
+
         int index = mTelephonyManager.getCdmaEnhancedRoamingIndicatorDisplayNumber();
         int phoneType = mTelephonyManager.getPhoneType();
         if (phoneType == TelephonyManager.PHONE_TYPE_CDMA) {
@@ -3720,9 +3757,7 @@
 
     @Test
     public void testNrDualConnectivityEnable() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         if (!ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> tm.isRadioInterfaceCapabilitySupported(
@@ -3766,10 +3801,8 @@
 
     @Test
     public void testCdmaRoamingMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
+                && mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
 
         // Save state
         int cdmaRoamingMode = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
@@ -3793,10 +3826,8 @@
 
     @Test
     public void testCdmaSubscriptionMode() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
-                || mTelephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_CDMA)
+                && mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA);
 
         // Save state
         int cdmaSubscriptionMode = ShellIdentityUtils.invokeMethodWithShellPermissions(
@@ -3820,9 +3851,8 @@
 
     @Test
     public void testPinResult() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         final String empty_pin = ""; // For getting current remaining pin attempt.
         final String pin = "fake_pin";
         final String puk = "fake_puk";
@@ -3870,10 +3900,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_nullRequest() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify NPE throws if set request with null object
         try {
@@ -3885,10 +3912,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_noPermission() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         final SignalStrengthUpdateRequest normalRequest =
                 new SignalStrengthUpdateRequest.Builder()
@@ -3914,10 +3938,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_systemThresholdReportingRequestedWhileIdle() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify system privileged app with permission LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH can
         // set systemThresholdReportingRequestedWhileIdle to true with empty thresholdInfos
@@ -3932,10 +3953,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_hysteresisDbSet() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify SE throws for app when set hysteresisDb in the SignalThresholdInfo
         SignalStrengthUpdateRequest requestWithHysteresisDbSet =
@@ -3962,10 +3980,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_hysteresisMsSet() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify SE throws for app when set hysteresisMs in the SignalThresholdInfo
         SignalStrengthUpdateRequest requestWithHysteresisMsSet =
@@ -3992,10 +4007,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_isEnabledSet() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify SE throws for app when set isEnabled in the SignalThresholdInfo
         SignalStrengthUpdateRequest requestWithThresholdIsEnabledSet =
@@ -4022,10 +4034,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_tooShortThresholds() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // verify SE throws if app set too short thresholds
         SignalStrengthUpdateRequest requestWithTooShortThresholds =
@@ -4050,10 +4059,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_tooLongThresholds() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // verify SE throws if app set too long thresholds
         SignalStrengthUpdateRequest requestWithTooLongThresholds =
@@ -4079,10 +4085,7 @@
 
     @Test
     public void testSetSignalStrengthUpdateRequest_duplicatedRequest() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         final SignalStrengthUpdateRequest normalRequest =
                 new SignalStrengthUpdateRequest.Builder()
@@ -4114,10 +4117,7 @@
 
     @Test
     public void testClearSignalStrengthUpdateRequest_nullRequest() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // Verify NPE should throw if clear request with null object
         try {
@@ -4129,10 +4129,7 @@
 
     @Test
     public void testClearSignalStrengthUpdateRequest_noPermission() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         final SignalStrengthUpdateRequest normalRequest =
                 new SignalStrengthUpdateRequest.Builder()
@@ -4158,10 +4155,7 @@
 
     @Test
     public void testClearSignalStrengthUpdateRequest_clearWithNoSet() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         SignalStrengthUpdateRequest requestNeverSetBefore = new SignalStrengthUpdateRequest
                 .Builder()
@@ -4180,9 +4174,7 @@
 
     @Test
     public void testSendThermalMitigationRequest() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         StringBuilder cmdBuilder = new StringBuilder();
         cmdBuilder.append(THERMAL_MITIGATION_COMMAND_BASE).append(ALLOW_PACKAGE_SUBCOMMAND)
@@ -4300,7 +4292,7 @@
 
     @Test
     public void testIsRadioInterfaceCapabilitySupported() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported("empty"));
         assertFalse(mTelephonyManager.isRadioInterfaceCapabilitySupported(null));
@@ -4309,7 +4301,7 @@
 
     @Test
     public void testGetAllCellInfo() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
 
         // For IRadio <1.5, just verify that calling the method doesn't throw an error.
         if (mRadioVersion < RADIO_HAL_VERSION_1_5) {
@@ -4504,9 +4496,10 @@
         return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+' || c == 'N';
     }
 
-    private int getValidSlotIndex() {
+    private Map.Entry<Integer, Integer> getValidSlotIndexAndPort() {
         return ShellIdentityUtils.invokeMethodWithShellPermissions(
                 mTelephonyManager, (tm) -> {
+
                     List<UiccCardInfo> cardInfos = mTelephonyManager.getUiccCardsInfo();
                     Set<String> presentCards = Arrays.stream(mTelephonyManager.getUiccSlotsInfo())
                             .filter(Objects::nonNull)
@@ -4518,11 +4511,15 @@
                             .map(s -> s.endsWith("F") ? s.substring(0, s.length() - 1) : s)
                             .collect(Collectors.toSet());
                     int slotIndex = -1;
+                    int portIndex = -1;
                     for (UiccCardInfo cardInfo : cardInfos) {
                         for (UiccPortInfo portInfo : cardInfo.getPorts()) {
                             if (presentCards.contains(portInfo.getIccId())
                                     || presentCards.contains(cardInfo.getEid())) {
                                 slotIndex = cardInfo.getPhysicalSlotIndex();
+                                portIndex = portInfo.getPortIndex();
+                                Log.d(TAG, "SlotIndex : " + slotIndex + " and portIndex :"
+                                        + portIndex);
                                 break;
                             }
                         }
@@ -4531,7 +4528,7 @@
                         fail("Test must be run with SIM card inserted, presentCards = "
                                 + presentCards + "cardinfos = " + cardInfos);
                     }
-                    return slotIndex;
+                    return Map.entry(slotIndex, portIndex);
                 });
     }
 
@@ -4549,7 +4546,7 @@
      * @return whether to proceed the test
      */
     private boolean supportSetFplmn() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+        if (!hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
             return false;
         }
         return mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
@@ -4596,12 +4593,6 @@
 
     @Test
     public void testRegisterTelephonyCallbackWithNonLooper() throws Throwable {
-        if (!InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
-            return;
-        }
-
         mMockSignalStrengthsTelephonyCallback = new MockSignalStrengthsTelephonyCallback();
 
         // Test register, generates an mOnSignalStrengthsChanged event
@@ -4642,12 +4633,6 @@
 
     @Test
     public void testRegisterTelephonyCallback() throws Throwable {
-        if (!InstrumentationRegistry.getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires PackageManager.FEATURE_TELEPHONY");
-            return;
-        }
-
         if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
             // TODO: temp workaround, need to adjust test to for CDMA
             return;
@@ -4738,9 +4723,8 @@
      */
     @Test
     public void testGetNetworkSlicingConfiguration() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_RADIO_ACCESS));
+
         CompletableFuture<NetworkSlicingConfig> resultFuture = new CompletableFuture<>();
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                 (tm) -> tm.getNetworkSlicingConfiguration(mSimpleExecutor, resultFuture::complete));
@@ -4748,6 +4732,8 @@
 
     @Test
     public void testCheckCarrierPrivilegesForPackageEnforcesReadPrivilege() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4763,7 +4749,8 @@
 
     @Test
     public void testCheckCarrierPrivilegesForPackageThrowsExceptionWithoutReadPrivilege() {
-        if (!hasCellular()) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             mTelephonyManager.checkCarrierPrivilegesForPackage(mSelfPackageName);
             fail("TelephonyManager#checkCarrierPrivilegesForPackage must be protected "
@@ -4775,6 +4762,8 @@
 
     @Test
     public void testCheckCarrierPrivilegesForPackageAnyPhone() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(mSelfPackageName);
         } catch (SecurityException e) {
@@ -4785,6 +4774,8 @@
 
     @Test
     public void testGetCarrierPackageNamesForIntentAndPhoneEnforcesReadPrivilege() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4802,7 +4793,8 @@
 
     @Test
     public void testGetCarrierPackageNamesForIntentAndPhoneThrowsExceptionWithoutReadPrivilege() {
-        if (!hasCellular()) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             Intent intent = new Intent();
             int phoneId = 1;
@@ -4819,6 +4811,8 @@
 
     @Test
     public void testGetPackagesWithCarrierPrivilegesEnforcesReadPrivilege() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
@@ -4834,7 +4828,8 @@
 
     @Test
     public void testGetPackagesWithCarrierPrivilegesThrowsExceptionWithoutReadPrivilege() {
-        if (!hasCellular()) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         try {
             mTelephonyManager.getPackagesWithCarrierPrivileges();
             fail("TelephonyManager#getPackagesWithCarrierPrivileges must be protected "
@@ -4846,7 +4841,8 @@
 
     @Test
     public void testSimSlotMapping() {
-        if (!hasCellular()) return;
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity("android.permission.MODIFY_PHONE_STATE");
         // passing slotMapping combination
@@ -4908,6 +4904,8 @@
 
     @Test
     public void getUiccSlotInfoTest() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
         UiccSlotInfo[] slotInfos = mTelephonyManager.getUiccSlotsInfo();
 
         if (slotInfos == null) {
@@ -4929,5 +4927,47 @@
             }
         }
     }
+
+    @Test
+    public void getSimSlotMappingTestReadPermission() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        try {
+            Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+            fail("Expected SecurityException, no READ_PRIVILEGED_PHONE_STATE permission");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+    @Test
+    public void getSimSlotMappingTest() {
+        assumeTrue(hasFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION));
+
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity("android.permission.READ_PRIVILEGED_PHONE_STATE");
+        try {
+            Collection<UiccSlotMapping> simSlotMapping = mTelephonyManager.getSimSlotMapping();
+            assertTrue(isSlotMappingValid(simSlotMapping));
+        } catch (IllegalArgumentException e) {
+            fail("IllegalArgumentException, Duplicate UiccSlotMapping data found");
+        } finally {
+            InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
+    private static boolean isSlotMappingValid(@NonNull Collection<UiccSlotMapping> slotMapping) {
+        // Grouping the collection by logicalSlotIndex, finding different entries mapping to the
+        // same logical slot
+        Map<Integer, List<UiccSlotMapping>> slotMappingInfo = slotMapping.stream().collect(
+                Collectors.groupingBy(UiccSlotMapping::getLogicalSlotIndex));
+        for (Map.Entry<Integer, List<UiccSlotMapping>> entry : slotMappingInfo.entrySet()) {
+            List<UiccSlotMapping> logicalSlotMap = entry.getValue();
+            if (logicalSlotMap.size() > 1) {
+                // duplicate logicalSlotIndex found
+                return false;
+            }
+        }
+        return true;
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
index 8a970e7..613ead9 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTestOnMockModem.java
@@ -15,7 +15,11 @@
  */
 package android.telephony.cts;
 
+import static com.android.internal.telephony.RILConstants.INTERNAL_ERR;
+import static com.android.internal.telephony.RILConstants.RIL_REQUEST_RADIO_POWER;
+
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
@@ -90,8 +94,7 @@
         sMockModem.resetState();
 
         sMockModem.unsolSimSlotsStatusChanged();
-        assertTrue(
-                sMockModem.waitForLatchCountdown(MockModemService.LATCH_MOCK_MODEM_SIM_READY));
+        assertTrue(sMockModem.waitForLatchCountdown(MockModemService.LATCH_MOCK_MODEM_SIM_READY));
 
         TimeUnit.SECONDS.sleep(1);
         simCardState = mTelephonyManager.getSimCardState();
@@ -100,6 +103,50 @@
     }
 
     @Test
+    public void testRadioPowerToggle() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerToggle");
+
+        int radioState = mTelephonyManager.getRadioPowerState();
+        Log.d(TAG, "Radio state: " + radioState);
+
+        // Toggle radio power
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.toggleRadioOnOff(),
+                    SecurityException.class,
+                    "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+        }
+
+        // Wait the radio state update in Framework
+        TimeUnit.SECONDS.sleep(1);
+        int toggleRadioState =
+                radioState == TelephonyManager.RADIO_POWER_ON
+                        ? TelephonyManager.RADIO_POWER_OFF
+                        : TelephonyManager.RADIO_POWER_ON;
+        assertEquals(mTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+        // Toggle radio power again back to original radio state
+        try {
+            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(
+                    mTelephonyManager,
+                    (tm) -> tm.toggleRadioOnOff(),
+                    SecurityException.class,
+                    "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#toggleRadioOnOff should require " + e);
+        }
+
+        // Wait the radio state update in Framework
+        TimeUnit.SECONDS.sleep(1);
+        assertEquals(mTelephonyManager.getRadioPowerState(), radioState);
+
+        Log.d(TAG, "Test Done ");
+    }
+
+    @Test
     public void testRadioPower() throws Throwable {
         Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPower");
 
@@ -184,4 +231,57 @@
 
         Log.d(TAG, "Test Done ");
     }
+
+    @Test
+    public void testRadioPowerWithFailureResults() throws Throwable {
+        Log.d(TAG, "TelephonyManagerTestOnMockModem#testRadioPowerWithFailureResults");
+
+        int radioState = mTelephonyManager.getRadioPowerState();
+        Log.d(TAG, "Radio state: " + radioState);
+
+        int toggleRadioState =
+                radioState == TelephonyManager.RADIO_POWER_ON
+                        ? TelephonyManager.RADIO_POWER_OFF
+                        : TelephonyManager.RADIO_POWER_ON;
+
+        // Force the returned response of RIL_REQUEST_RADIO_POWER as INTERNAL_ERR
+        sMockModem.forceErrorResponse(RIL_REQUEST_RADIO_POWER, INTERNAL_ERR);
+
+        boolean result = false;
+        try {
+            boolean state = (toggleRadioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+            result =
+                    ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                            mTelephonyManager,
+                            (tm) -> tm.setRadioPower(state),
+                            SecurityException.class,
+                            "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+        }
+
+        TimeUnit.SECONDS.sleep(1);
+        assertTrue(result);
+        assertNotEquals(mTelephonyManager.getRadioPowerState(), toggleRadioState);
+
+        // Reset the modified error response of RIL_REQUEST_RADIO_POWER to the original behavior
+        // and -1 means to disable the modifed mechanism in mock modem
+        sMockModem.forceErrorResponse(RIL_REQUEST_RADIO_POWER, -1);
+
+        // Recovery the power state back to original radio state
+        try {
+            boolean state = (radioState == TelephonyManager.RADIO_POWER_ON) ? true : false;
+            result =
+                    ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
+                            mTelephonyManager,
+                            (tm) -> tm.setRadioPower(state),
+                            SecurityException.class,
+                            "android.permission.MODIFY_PHONE_STATE");
+        } catch (SecurityException e) {
+            Log.d(TAG, "TelephonyManager#setRadioPower should require " + e);
+        }
+        TimeUnit.SECONDS.sleep(1);
+        assertTrue(result);
+        assertEquals(mTelephonyManager.getRadioPowerState(), radioState);
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java b/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java
new file mode 100644
index 0000000..95b7f8b
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/cts/UiccSlotMappingTest.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.UiccSlotMapping;
+
+import org.junit.Test;
+
+public class UiccSlotMappingTest {
+    private static final int PORT_INDEX = 0;
+    private static final int PHYSICAL_SLOT_INDEX = 0;
+    private static final int LOGICAL_SLOT_INDEX = 0;
+
+    @Test
+    public void testConstructorAndGetters() {
+        UiccSlotMapping uiccSlotMapping = new UiccSlotMapping(
+                PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+        assertThat(uiccSlotMapping.getPortIndex()).isEqualTo(PORT_INDEX);
+        assertThat(uiccSlotMapping.getPhysicalSlotIndex()).isEqualTo(PHYSICAL_SLOT_INDEX);
+        assertThat(uiccSlotMapping.getLogicalSlotIndex()).isEqualTo(LOGICAL_SLOT_INDEX);
+    }
+
+    @Test
+    public void testEquals() {
+        UiccSlotMapping uiccSlotMappingObject = new UiccSlotMapping(
+                PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+        UiccSlotMapping uiccSlotMappingEqualObject = new UiccSlotMapping(
+                PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+        assertThat(uiccSlotMappingObject).isEqualTo(uiccSlotMappingEqualObject);
+    }
+
+    @Test
+    public void testNotEqual() {
+        UiccSlotMapping uiccSlotMappingObject = new UiccSlotMapping(
+                PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+        UiccSlotMapping uiccSlotMappingNotEqualObject = new UiccSlotMapping(
+                /* portIndex= */ 0, /* phycalSlotIndex= */1, /* logicalSlotIndex= */ 1);
+
+        assertThat(uiccSlotMappingObject).isNotEqualTo(uiccSlotMappingNotEqualObject);
+    }
+
+    @Test
+    public void testParcel() {
+        UiccSlotMapping uiccSlotMapping = new UiccSlotMapping(
+                PORT_INDEX, PHYSICAL_SLOT_INDEX, LOGICAL_SLOT_INDEX);
+
+        Parcel parcel = Parcel.obtain();
+        uiccSlotMapping.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        UiccSlotMapping uiccSlotMappingParcel = UiccSlotMapping.CREATOR.createFromParcel(parcel);
+
+        assertThat(uiccSlotMapping).isEqualTo(uiccSlotMappingParcel);
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 31ba58f..6c19696 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
 import android.content.BroadcastReceiver;
@@ -40,7 +41,6 @@
 import android.os.ParcelFileDescriptor;
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
-import androidx.annotation.Nullable;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -52,6 +52,12 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
 import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.InputStream;
@@ -66,10 +72,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
 public class VisualVoicemailServiceTest {
 
     private static final String TAG = "VvmServiceTest";
@@ -96,20 +98,20 @@
     @Before
     public void setUp() throws Exception {
         mContext = getInstrumentation().getContext();
-        if (hasTelephony(mContext)) {
-            mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
-            setDefaultDialer(getInstrumentation(), PACKAGE);
+        assumeTrue(hasFeatureSupported(mContext));
 
-            TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
-            mPhoneAccountHandle = telecomManager
-                    .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
-            assertNotNull(mPhoneAccountHandle);
-            mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle);
-            assertNotNull(mPhoneNumber, "Tests require a line1 number for the active SIM.");
+        mPreviousDefaultDialer = getDefaultDialer(getInstrumentation());
+        setDefaultDialer(getInstrumentation(), PACKAGE);
 
-            mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
-                    .createForPhoneAccountHandle(mPhoneAccountHandle);
-        }
+        TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+        mPhoneAccountHandle = telecomManager
+                .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL);
+        assertNotNull(mPhoneAccountHandle);
+        mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle);
+        assertNotNull(mPhoneNumber, "Tests require a line1 number for the active SIM.");
+
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class)
+                .createForPhoneAccountHandle(mPhoneAccountHandle);
 
         PackageManager packageManager = mContext.getPackageManager();
         packageManager.setComponentEnabledSetting(
@@ -122,7 +124,7 @@
 
     @After
     public void tearDown() throws Exception {
-        if (hasTelephony(mContext)) {
+        if (hasFeatureSupported(mContext)) {
             if (!TextUtils.isEmpty(mPreviousDefaultDialer)) {
                 setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer);
             }
@@ -135,11 +137,6 @@
 
     @Test
     public void testPermissionlessService_ignored() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
-
         PackageManager packageManager = mContext.getPackageManager();
         packageManager.setComponentEnabledSetting(
                 new ComponentName(mContext, MockVisualVoicemailService.class),
@@ -184,10 +181,6 @@
 
     @Test
     public void testFilter() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSms result = getSmsFromText("//CTSVVM",
                 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
 
@@ -204,10 +197,6 @@
 
     @Test
     public void testFilter_data() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         if (!hasDataSms()) {
             Log.d(TAG, "skipping test that requires data SMS feature");
             return;
@@ -233,10 +222,6 @@
 
     @Test
     public void testFilter_TrailingSemiColon() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSms result = getSmsFromText("//CTSVVM",
                 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1;");
 
@@ -253,10 +238,6 @@
 
     @Test
     public void testFilter_EmptyPrefix() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSms result = getSmsFromText("//CTSVVM",
                 "//CTSVVM::st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
 
@@ -273,10 +254,6 @@
 
     @Test
     public void testFilter_EmptyField() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSms result = getSmsFromText("//CTSVVM",
                 "//CTSVVM:STATUS:");
         assertTrue(result.getFields().isEmpty());
@@ -284,90 +261,54 @@
 
     @Test
     public void testFilterFail_NotVvm() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "helloworld");
     }
 
     @Test
     public void testFilterFail_PrefixMismatch() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//FOOVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
     }
 
     @Test
     public void testFilterFail_MissingFirstColon() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVMSTATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
     }
 
     @Test
     public void testFilterFail_MissingSecondColon() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVM:STATUSst=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1");
     }
 
     @Test
     public void testFilterFail_MessageEndAfterClientPrefix() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVM:");
     }
 
     @Test
     public void testFilterFail_MessageEndAfterPrefix() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVM:STATUS");
     }
 
     @Test
     public void testFilterFail_InvalidKeyValuePair() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVM:STATUS:key");
     }
 
     @Test
     public void testFilterFail_InvalidMissingKey() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertVisualVoicemailSmsNotReceived("//CTSVVM",
                 "//CTSVVM:STATUS:=value");
     }
 
     @Test
     public void testFilter_MissingValue() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSms result = getSmsFromText("//CTSVVM",
                 "//CTSVVM:STATUS:key=");
         assertEquals("STATUS", result.getPrefix());
@@ -376,10 +317,6 @@
 
     @Test
     public void testFilter_originatingNumber_match_filtered() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder()
                 .setClientPrefix("//CTSVVM")
                 .setOriginatingNumbers(Arrays.asList(mPhoneNumber))
@@ -390,10 +327,6 @@
 
     @Test
     public void testFilter_originatingNumber_mismatch_notFiltered() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder()
                 .setClientPrefix("//CTSVVM")
                 .setOriginatingNumbers(Arrays.asList("1"))
@@ -404,10 +337,6 @@
 
     @Test
     public void testFilter_port_match() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         if (!hasDataSms()) {
             Log.d(TAG, "skipping test that requires data SMS feature");
             return;
@@ -423,10 +352,6 @@
 
     @Test
     public void testFilter_port_mismatch() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         if (!hasDataSms()) {
             Log.d(TAG, "skipping test that requires data SMS feature");
             return;
@@ -442,10 +367,6 @@
 
     @Test
     public void testFilter_port_anydata() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         if (!hasDataSms()) {
             Log.d(TAG, "skipping test that requires data SMS feature");
             return;
@@ -464,10 +385,6 @@
      */
     @Test
     public void testFilter_port_anydata_notData() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         if (!hasDataSms()) {
             Log.d(TAG, "skipping test that requires data SMS feature");
             return;
@@ -483,10 +400,6 @@
 
     @Test
     public void testGetVisualVoicemailPackageName_isSelf() {
-        if (!hasTelephony(mContext)) {
-            Log.d(TAG, "skipping test that requires telephony feature");
-            return;
-        }
         assertEquals(PACKAGE, mTelephonyManager.getVisualVoicemailPackageName());
     }
 
@@ -704,10 +617,9 @@
         }
     }
 
-    private static boolean hasTelephony(Context context) {
-        final PackageManager packageManager = context.getPackageManager();
-        return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) &&
-                packageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE);
+    private static boolean hasFeatureSupported(Context context) {
+        return context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY_CALLING);
     }
 
     private boolean hasDataSms() {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
index eae33de..c871586 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccManagerTest.java
@@ -213,6 +213,36 @@
     }
 
     @Test
+    public void testSwitchToSubscriptionWithCallback() {
+        // test disabled state only for now
+        if (mEuiccManager.isEnabled()) {
+            return;
+        }
+
+        // set up CountDownLatch and receiver
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        mCallbackReceiver = new CallbackReceiver(countDownLatch);
+        getContext()
+                .registerReceiver(
+                        mCallbackReceiver, new IntentFilter(ACTION_SWITCH_TO_SUBSCRIPTION));
+
+        // call deleteSubscription()
+        PendingIntent callbackIntent = createCallbackIntent(ACTION_SWITCH_TO_SUBSCRIPTION);
+        mEuiccManager.switchToSubscription(4, TelephonyManager.DEFAULT_PORT_INDEX, callbackIntent);
+
+        // wait for callback
+        try {
+            countDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        // verify correct result code is received
+        assertEquals(
+                EuiccManager.EMBEDDED_SUBSCRIPTION_RESULT_ERROR, mCallbackReceiver.getResultCode());
+    }
+
+    @Test
     public void testEraseSubscriptions() {
         // test disabled state only for now
         if (mEuiccManager.isEnabled()) {
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
index c506e8e6..9416ba3 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
@@ -69,6 +69,7 @@
     private static final int CALLBACK_TIMEOUT_MILLIS = 2000 /* 2 sec */;
 
     private static final int MOCK_SLOT_ID = 1;
+    private static final int MOCK_PORT_ID = 0;
     private static final String MOCK_ICCID = "12345";
     private static final String MOCK_NICK_NAME = "nick name";
 
@@ -130,6 +131,12 @@
         }
 
         @Override
+        public int onSwitchToSubscriptionWithPort(int slotId, int portIndex, String iccid,
+                boolean forceDeactivateSim) {
+            return 0;
+        }
+
+        @Override
         public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
             return 0;
         }
@@ -416,6 +423,7 @@
 
         mEuiccServiceBinder.switchToSubscription(
                 MOCK_SLOT_ID,
+                MOCK_PORT_ID,
                 MOCK_ICCID,
                 true /*forceDeactivateSim*/,
                 new ISwitchToSubscriptionCallback.Stub() {
@@ -423,7 +431,8 @@
                     public void onComplete(int result) {
                         assertEquals(EuiccService.RESULT_OK, result);
                     }
-                });
+                },
+                false /* usePortIndex */);
 
         try {
             mCountDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
index 52dd47c..698ac75 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
@@ -147,6 +147,13 @@
     }
 
     @Override
+    public @Result int onSwitchToSubscriptionWithPort(
+            int slotId, int portIndex, @Nullable String iccid, boolean forceDeactivateSim) {
+        sMockEuiccServiceCallback.setMethodCalled();
+        return EuiccService.RESULT_OK;
+    }
+
+    @Override
     public int onUpdateSubscriptionNickname(int slotId, String iccid, String nickname) {
         sMockEuiccServiceCallback.setMethodCalled();
         return EuiccService.RESULT_OK;
diff --git a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
index 0a16299..985aca9 100644
--- a/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/gba/cts/GbaServiceTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.Instrumentation;
 import android.content.pm.PackageManager;
@@ -97,9 +98,7 @@
 
     @Before
     public void setUp() throws Exception {
-        if (!isFeatureSupported()) {
-            return;
-        }
+        assumeTrue(isFeatureSupported());
 
         setService(SERVICE_PACKAGE);
         setReleaseTime(RELEASE_DEFAULT);
@@ -107,20 +106,12 @@
 
     @Test (expected = SecurityException.class)
     public void testPermissions() {
-        if (!isFeatureSupported()) {
-            throw new SecurityException("Feaure is not supported");
-        }
-
         runGbaFailCase(TelephonyManager.GBA_FAILURE_REASON_FEATURE_NOT_SUPPORTED,
                 android.Manifest.permission.READ_PHONE_STATE);
     }
 
     @Test
     public void testAuthSuccess() {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         Random rand = new Random();
 
         for (int i = 0; i < 20; i++) {
@@ -172,10 +163,6 @@
 
     @Test
     public void testGbaNotSupported() throws Exception {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         setService("");
         sConfig.setConfig(true, new byte[16], BTID, TelephonyManager.GBA_FAILURE_REASON_UNKNOWN);
 
@@ -187,10 +174,6 @@
 
     @Test
     public void testAuthFail() {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         for (int r = TelephonyManager.GBA_FAILURE_REASON_UNKNOWN;
                 r <= TelephonyManager.GBA_FAILURE_REASON_SECURITY_PROTOCOL_NOT_SUPPORTED; r++) {
             sConfig.setConfig(false, new byte[16], BTID, r);
@@ -235,10 +218,6 @@
 
     @Test
     public void testServiceReleaseDefault() throws Exception {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         int ss = sConfig.getServiceState();
         boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
                 || ss == TestGbaConfig.STATE_REMOVED
@@ -256,10 +235,6 @@
 
     @Test
     public void testServiceReleaseDuration() throws Exception {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         int ss = sConfig.getServiceState();
         boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
                 || ss == TestGbaConfig.STATE_REMOVED
@@ -281,10 +256,6 @@
 
     @Test
     public void testServiceNoRelease() throws Exception {
-        if (!isFeatureSupported()) {
-            return;
-        }
-
         int ss = sConfig.getServiceState();
         boolean isExpected = (ss == TestGbaConfig.STATE_UNKNOWN
                 || ss == TestGbaConfig.STATE_REMOVED
@@ -325,7 +296,7 @@
 
     private static boolean isFeatureSupported() {
         if (!InstrumentationRegistry.getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY)) {
+                PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION)) {
             return false;
         }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
new file mode 100644
index 0000000..9a53194
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsCallingTest.java
@@ -0,0 +1,1092 @@
+/*
+ * 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 static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.PersistableBundle;
+import android.telecom.Call;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.InCallServiceStateValidator;
+import android.telephony.cts.InCallServiceStateValidator.InCallServiceCallbacks;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.ims.feature.ImsFeature;
+import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsFeatureConfiguration;
+import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+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.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * CTS tests for ImsCall .
+ */
+@RunWith(AndroidJUnit4.class)
+public class ImsCallingTest {
+
+    private static ImsServiceConnector sServiceConnector;
+
+    private static final String LOG_TAG = "CtsImsCallingTest";
+    private static final String PACKAGE = "android.telephony.ims.cts";
+    private static final String PACKAGE_CTS_DIALER = "android.telephony.cts";
+    private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer ";
+    private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer";
+
+    public static final int WAIT_FOR_SERVICE_TO_UNBOUND = 40000;
+    public static final int WAIT_FOR_CONDITION = 3000;
+    public static final int WAIT_FOR_CALL_STATE = 10000;
+    public static final int WAIT_FOR_CALL_DISCONNECT = 5000;
+    public static final int WAIT_FOR_CALL_CONNECT = 5000;
+    public static final int WAIT_FOR_CALL_STATE_HOLD = 3000;
+    public static final int WAIT_FOR_CALL_STATE_RESUME = 3000;
+    public static final int WAIT_FOR_CALL_STATE_ACTIVE = 15000;
+    public static final int LATCH_WAIT = 0;
+    public static final int LATCH_INCALL_SERVICE_BOUND = 1;
+    public static final int LATCH_INCALL_SERVICE_UNBOUND = 2;
+    public static final int LATCH_IS_ON_CALL_ADDED = 3;
+    public static final int LATCH_IS_ON_CALL_REMOVED = 4;
+    public static final int LATCH_IS_CALL_DIALING = 5;
+    public static final int LATCH_IS_CALL_ACTIVE = 6;
+    public static final int LATCH_IS_CALL_DISCONNECTING = 7;
+    public static final int LATCH_IS_CALL_DISCONNECTED = 8;
+    public static final int LATCH_IS_CALL_RINGING = 9;
+    public static final int LATCH_IS_CALL_HOLDING = 10;
+    public static final int LATCH_MAX = 11;
+
+    private static boolean sIsBound = false;
+    private static int sCounter = 5553639;
+    private static int sTestSlot = 0;
+    private static int sTestSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    private static long sPreviousOptInStatus = 0;
+    private static long sPreviousEn4GMode = 0;
+    private static String sPreviousDefaultDialer;
+
+    private static CarrierConfigReceiver sReceiver;
+    private static SubscriptionManager sSubcriptionManager;
+
+    private final Object mLock = new Object();
+    private InCallServiceCallbacks mServiceCallBack;
+    private Context mContext;
+    private ConcurrentHashMap<String, Call> mCalls = new ConcurrentHashMap<String, Call>();
+    private String mCurrentCallId = null;
+    private Call mCall1 = null;
+    private Call mCall2 = null;
+    private TestImsCallSessionImpl mCallSession1 = null;
+    private TestImsCallSessionImpl mCallSession2 = null;
+
+    private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+    static {
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+    }
+
+    public boolean callingTestLatchCountdown(int latchIndex, int waitMs) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+             //complete == false
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public void countDownLatch(int latchIndex) {
+        synchronized (mLock) {
+            sLatches[latchIndex].countDown();
+        }
+    }
+
+    private abstract static class BaseReceiver extends BroadcastReceiver {
+        protected CountDownLatch mLatch = new CountDownLatch(1);
+
+        void clearQueue() {
+            mLatch = new CountDownLatch(1);
+        }
+
+        void waitForChanged() throws Exception {
+            mLatch.await(5000, TimeUnit.MILLISECONDS);
+        }
+    }
+
+    private static class CarrierConfigReceiver extends BaseReceiver {
+        private final int mSubId;
+
+        CarrierConfigReceiver(int subId) {
+            mSubId = subId;
+        }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(intent.getAction())) {
+                int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, -1);
+                if (mSubId == subId) {
+                    mLatch.countDown();
+                }
+            }
+        }
+    }
+
+    public interface Condition {
+        Object expected();
+        Object actual();
+    }
+
+    void sleep(long ms) {
+        try {
+            Thread.sleep(ms);
+        } catch (Exception e) {
+            Log.d(LOG_TAG, "InterruptedException");
+        }
+    }
+
+    void waitUntilConditionIsTrueOrTimeout(Condition condition, long timeout,
+            String description) {
+        final long start = System.currentTimeMillis();
+        while (!Objects.equals(condition.expected(), condition.actual())
+                && System.currentTimeMillis() - start < timeout) {
+            sleep(50);
+        }
+        assertEquals(description, condition.expected(), condition.actual());
+    }
+
+    @BeforeClass
+    public static void beforeAllTests() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        TelephonyManager tm = (TelephonyManager) getContext()
+                .getSystemService(Context.TELEPHONY_SERVICE);
+        sTestSub = ImsUtils.getPreferredActiveSubId();
+        sTestSlot = SubscriptionManager.getSlotIndex(sTestSub);
+        if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+            return;
+        }
+
+        sServiceConnector = new ImsServiceConnector(InstrumentationRegistry.getInstrumentation());
+        // Remove all live ImsServices until after these tests are done
+        sServiceConnector.clearAllActiveImsServices(sTestSlot);
+
+        sReceiver = new CarrierConfigReceiver(sTestSub);
+        IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
+        InstrumentationRegistry.getInstrumentation().getContext()
+                .registerReceiver(sReceiver, filter);
+
+        UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            ui.adoptShellPermissionIdentity();
+            // Get the default dialer and save it to restore after test ends.
+            sPreviousDefaultDialer = getDefaultDialer(InstrumentationRegistry.getInstrumentation());
+            // Set dialer as "android.telephony.cts"
+            setDefaultDialer(InstrumentationRegistry.getInstrumentation(), PACKAGE_CTS_DIALER);
+
+            sSubcriptionManager = InstrumentationRegistry.getInstrumentation()
+                    .getContext().getSystemService(SubscriptionManager.class);
+            // Get the default Subscription values and save it to restore after test ends.
+            sPreviousOptInStatus = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+                        SubscriptionManager.VOIMS_OPT_IN_STATUS, 0, getContext());
+            sPreviousEn4GMode = sSubcriptionManager.getLongSubscriptionProperty(sTestSub,
+                        SubscriptionManager.ENHANCED_4G_MODE_ENABLED, 0, getContext());
+            // Set the new Sunbscription values
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(1));
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(1));
+
+            //Override the carrier configurartions
+            CarrierConfigManager configurationManager = InstrumentationRegistry.getInstrumentation()
+                    .getContext().getSystemService(CarrierConfigManager.class);
+            PersistableBundle bundle = new PersistableBundle(1);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_AVAILABLE_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, false);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true);
+            bundle.putBoolean(CarrierConfigManager.KEY_CARRIER_IMS_GBA_REQUIRED_BOOL , false);
+
+            sReceiver.clearQueue();
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(configurationManager,
+                    (m) -> m.overrideConfig(sTestSub, bundle));
+        } finally {
+            ui.dropShellPermissionIdentity();
+        }
+        sReceiver.waitForChanged();
+    }
+
+    @AfterClass
+    public static void afterAllTests() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        UiAutomation ui = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            ui.adoptShellPermissionIdentity();
+            // Set the default Sunbscription values.
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.VOIMS_OPT_IN_STATUS, String.valueOf(sPreviousOptInStatus));
+            sSubcriptionManager.setSubscriptionProperty(sTestSub,
+                    SubscriptionManager.ENHANCED_4G_MODE_ENABLED, String.valueOf(
+                    sPreviousEn4GMode));
+            // Set default dialer
+            setDefaultDialer(InstrumentationRegistry.getInstrumentation(), sPreviousDefaultDialer);
+
+            // Restore all ImsService configurations that existed before the test.
+            if (sServiceConnector != null && sIsBound) {
+                sServiceConnector.disconnectServices();
+                sIsBound = false;
+            }
+            sServiceConnector = null;
+            overrideCarrierConfig(null);
+
+            if (sReceiver != null) {
+                InstrumentationRegistry.getInstrumentation().getContext()
+                        .unregisterReceiver(sReceiver);
+                sReceiver = null;
+            }
+        } finally {
+            ui.dropShellPermissionIdentity();
+        }
+    }
+
+    @Before
+    public void beforeTest() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        TelephonyManager tm = (TelephonyManager) InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        if (tm.getSimState(sTestSlot) != TelephonyManager.SIM_STATE_READY) {
+            fail("This test requires that there is a SIM in the device!");
+        }
+        // Correctness check: ensure that the subscription hasn't changed between tests.
+        int[] subs = SubscriptionManager.getSubId(sTestSlot);
+
+        if (subs == null) {
+            fail("This test requires there is an active subscription in slot " + sTestSlot);
+        }
+        boolean isFound = false;
+        for (int sub : subs) {
+            isFound |= (sTestSub == sub);
+        }
+        if (!isFound) {
+            fail("Invalid state found: the test subscription in slot " + sTestSlot + " changed "
+                    + "during this test.");
+        }
+    }
+
+    public void bindImsService() throws Exception  {
+        // Connect to the ImsService with the MmTel feature.
+        assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL)
+                .build()));
+        sIsBound = true;
+        // The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_MMTEL);
+        assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
+                sServiceConnector.getCarrierService().getMmTelFeature());
+
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_MMTEL_CAP_SET);
+
+        MmTelFeature.MmTelCapabilities capabilities = new MmTelFeature.MmTelCapabilities(
+                MmTelFeature.MmTelCapabilities.CAPABILITY_TYPE_VOICE);
+        // Set Registered and VoLTE capable
+        sServiceConnector.getCarrierService().getImsService().getRegistration(0).onRegistered(
+                ImsRegistrationImplBase.REGISTRATION_TECH_LTE);
+        sServiceConnector.getCarrierService().getMmTelFeature().setCapabilities(capabilities);
+        sServiceConnector.getCarrierService().getMmTelFeature()
+                .notifyCapabilitiesStatusChanged(capabilities);
+
+        // Wait a second for the notifyCapabilitiesStatusChanged indication to be processed on the
+        // main telephony thread - currently no better way of knowing that telephony has processed
+        // this command. SmsManager#isImsSmsSupported() is @hide and must be updated to use new API.
+        Thread.sleep(3000);
+    }
+
+    @After
+    public void afterTest() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        if (!mCalls.isEmpty() && (mCurrentCallId != null)) {
+            Call call = mCalls.get(mCurrentCallId);
+            call.disconnect();
+        }
+
+        if (sServiceConnector != null && sIsBound) {
+            sServiceConnector.disconnectCarrierImsService();
+            sServiceConnector.disconnectServices();
+            sIsBound = false;
+        }
+    }
+
+    @Test
+    public void testOutGoingCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallStartFailed() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+                                .getMmTelFeature();
+                        return (mmtelfeatue.isCallSessionCreated()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "CallSession Created");
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+        assertNotNull("Unable to get callSession, its null", callSession);
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_MO_FAILED);
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testIncomingCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        Bundle extras = new Bundle();
+        sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        if (call.getDetails().getState() == call.STATE_RINGING) {
+            callingTestLatchCountdown(LATCH_WAIT, 5000);
+            call.answer(0);
+        }
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        callSession.terminateIncomingCall();
+
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallForExecutor() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        sServiceConnector.setExecutorTestType(true);
+        bindImsService();
+
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallHoldResume() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        // Put on hold
+        call.hold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        // Put on resume
+        call.unhold();
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallHoldFailure() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_HOLD_FAILED);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        call.hold();
+        assertTrue("call is not in Active State", (call.getDetails().getState()
+                == call.STATE_ACTIVE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+
+    @Test
+    public void testOutGoingCallResumeFailure() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call call = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl callSession = sServiceConnector.getCarrierService().getMmTelFeature()
+                .getImsCallsession();
+
+        isCallActive(call, callSession);
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_HOLD);
+        // Put on hold
+        call.hold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+
+        callSession.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        call.unhold();
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE);
+        assertTrue("Call is not in Hold State", (call.getDetails().getState()
+                == call.STATE_HOLDING));
+
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        call.disconnect();
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(call, callSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingIncomingMultiCall() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        final Uri imsUri = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter), null);
+        Bundle extras = new Bundle();
+
+        // Place outgoing call
+        telecomManager.placeCall(imsUri, extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call moCall = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+
+        TestImsCallSessionImpl moCallSession = sServiceConnector.getCarrierService()
+                .getMmTelFeature().getImsCallsession();
+        isCallActive(moCall, moCallSession);
+        assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        extras.putBoolean("android.telephony.ims.feature.extra.IS_USSD", false);
+        extras.putBoolean("android.telephony.ims.feature.extra.IS_UNKNOWN_CALL", false);
+        extras.putString("android:imsCallID",  String.valueOf(++sCounter));
+        extras.putLong("android:phone_id", 123456);
+        sServiceConnector.getCarrierService().getMmTelFeature().onIncomingCallReceived(extras);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        Call mtCall = null;
+        if (mCurrentCallId != null) {
+            mtCall = getCall(mCurrentCallId);
+            if (mtCall.getDetails().getState() == Call.STATE_RINGING) {
+                callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_CONNECT);
+                mtCall.answer(0);
+            }
+        }
+
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        TestImsCallSessionImpl mtCallSession = sServiceConnector.getCarrierService()
+                .getMmTelFeature().getImsCallsession();
+        isCallActive(mtCall, mtCallSession);
+        assertTrue("Call is not in Active State", (mtCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        mtCall.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mtCall, mtCallSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        isCallActive(moCall, moCallSession);
+        assertTrue("Call is not in Active State", (moCall.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        moCall.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(moCall, moCallSession);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallSwap() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+
+        // Swap the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        mCall1.unhold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall2.getDetails().getState()
+                == Call.STATE_HOLDING));
+        isCallActive(mCall1, mCallSession1);
+
+        // After successful call swap disconnect the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_DISCONNECT);
+        mCall1.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall1, mCallSession1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        //Wait till second call is in active state
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+                                ? true : false;
+                    }
+                }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+        isCallActive(mCall2, mCallSession2);
+        mCall2.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall2, mCallSession2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    @Test
+    public void testOutGoingCallSwapFail() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        bindImsService();
+        mServiceCallBack = new ServiceCallBack();
+        InCallServiceStateValidator.setCallbacks(mServiceCallBack);
+        addOutgoingCalls();
+
+        mCallSession1.addTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        // Swap the call
+        callingTestLatchCountdown(LATCH_WAIT, WAIT_FOR_CALL_STATE_RESUME);
+        mCall1.unhold();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+                == Call.STATE_HOLDING));
+
+        // Wait till second call is in active state
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (mCall2.getDetails().getState() == Call.STATE_ACTIVE)
+                                ? true : false;
+                    }
+                }, WAIT_FOR_CALL_STATE_ACTIVE, "Call in Active State");
+
+        isCallActive(mCall2, mCallSession2);
+        mCallSession1.removeTestType(TestImsCallSessionImpl.TEST_TYPE_RESUME_FAILED);
+        mCall2.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall2, mCallSession2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        // Wait till second call is in active state
+        isCallActive(mCall1, mCallSession1);
+        mCall1.disconnect();
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTING, WAIT_FOR_CALL_STATE));
+        isCallDisconnected(mCall1, mCallSession1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_REMOVED, WAIT_FOR_CALL_STATE));
+
+        resetCallSessionObjects();
+        waitForUnboundService();
+    }
+
+    private void addOutgoingCalls() throws Exception {
+        TelecomManager telecomManager = (TelecomManager) InstrumentationRegistry
+                .getInstrumentation().getContext().getSystemService(Context.TELECOM_SERVICE);
+
+        // Place first outgoing call
+        final Uri imsUri1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+                null);
+        Bundle extras1 = new Bundle();
+
+        telecomManager.placeCall(imsUri1, extras1);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        mCall1 = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        mCallSession1 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+        isCallActive(mCall1, mCallSession1);
+        assertTrue("Call is not in Active State", (mCall1.getDetails().getState()
+                == Call.STATE_ACTIVE));
+
+        // Place second outgoing call
+        final Uri imsUri2 = Uri.fromParts(PhoneAccount.SCHEME_TEL, String.valueOf(++sCounter),
+                null);
+        Bundle extras2 = new Bundle();
+
+        telecomManager.placeCall(imsUri2, extras2);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_ON_CALL_ADDED, WAIT_FOR_CALL_STATE));
+
+        mCall2 = getCall(mCurrentCallId);
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DIALING, WAIT_FOR_CALL_STATE));
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_HOLDING, WAIT_FOR_CALL_STATE));
+        assertTrue("Call is not in Hold State", (mCall1.getDetails().getState()
+                == Call.STATE_HOLDING));
+
+        //Wait till the object of TestImsCallSessionImpl for second call created.
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        TestMmTelFeature mmtelfeatue = sServiceConnector.getCarrierService()
+                                .getMmTelFeature();
+                        return (mmtelfeatue.getImsCallsession() != mCallSession1) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "CallSession Created");
+
+        mCallSession2 = sServiceConnector.getCarrierService().getMmTelFeature().getImsCallsession();
+        isCallActive(mCall2, mCallSession2);
+        assertTrue("Call is not in Active State", (mCall2.getDetails().getState()
+                == Call.STATE_ACTIVE));
+    }
+
+    private void resetCallSessionObjects() {
+        mCall1 = mCall2 = null;
+        mCallSession1 = mCallSession2 = null;
+    }
+
+    public void waitForUnboundService() {
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        InCallServiceStateValidator inCallService = mServiceCallBack.getService();
+                        return (inCallService.isServiceUnBound()) ? true : false;
+                    }
+                }, WAIT_FOR_SERVICE_TO_UNBOUND, "Service Unbound");
+    }
+
+    public void isCallActive(Call call, TestImsCallSessionImpl callsession) {
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_ACTIVE, WAIT_FOR_CALL_STATE));
+        assertNotNull("Unable to get callSession, its null", callsession);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (callsession.isInCall()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "Call Active");
+    }
+
+    public void isCallDisconnected(Call call, TestImsCallSessionImpl callsession) {
+        assertTrue(callingTestLatchCountdown(LATCH_IS_CALL_DISCONNECTED, WAIT_FOR_CALL_STATE));
+        assertNotNull("Unable to get callSession, its null", callsession);
+
+        waitUntilConditionIsTrueOrTimeout(
+                new Condition() {
+                    @Override
+                    public Object expected() {
+                        return true;
+                    }
+
+                    @Override
+                    public Object actual() {
+                        return (callsession.isInTerminated()) ? true : false;
+                    }
+                }, WAIT_FOR_CONDITION, "Call Disconnected");
+    }
+
+    private void setCallID(String callid) {
+        assertNotNull("Call Id is set to null", callid);
+        mCurrentCallId = callid;
+    }
+
+    public void addCall(Call call) {
+        String callid = getCallId(call);
+        setCallID(callid);
+        synchronized (mCalls) {
+            mCalls.put(callid, call);
+        }
+    }
+
+    public String getCallId(Call call) {
+        String str = call.toString();
+        String[] arrofstr = str.split(",", 3);
+        int index = arrofstr[0].indexOf(":");
+        String callId = arrofstr[0].substring(index + 1);
+        return callId;
+    }
+
+    public Call getCall(String callId) {
+        synchronized (mCalls) {
+            if (mCalls.isEmpty()) {
+                return null;
+            }
+
+            for (Map.Entry<String, Call> entry : mCalls.entrySet()) {
+                if (entry.getKey().equals(callId)) {
+                    Call call = entry.getValue();
+                    assertNotNull("Call is not added, its null", call);
+                    return call;
+                }
+            }
+        }
+        return null;
+    }
+
+    private void removeCall(Call call) {
+        if (mCalls.isEmpty()) {
+            return;
+        }
+
+        String callid = getCallId(call);
+        Map.Entry<String, Call>[] entries = mCalls.entrySet().toArray(new Map.Entry[mCalls.size()]);
+        for (Map.Entry<String, Call> entry : entries) {
+            if (entry.getKey().equals(callid)) {
+                mCalls.remove(entry.getKey());
+                mCurrentCallId = null;
+            }
+        }
+    }
+
+    class ServiceCallBack extends InCallServiceCallbacks {
+
+        @Override
+        public void onCallAdded(Call call, int numCalls) {
+            Log.i(LOG_TAG, "onCallAdded, Call: " + call + ", Num Calls: " + numCalls);
+            addCall(call);
+            countDownLatch(LATCH_IS_ON_CALL_ADDED);
+        }
+
+        @Override
+        public void onCallRemoved(Call call, int numCalls) {
+            Log.i(LOG_TAG, "onCallRemoved, Call: " + call + ", Num Calls: " + numCalls);
+            removeCall(call);
+            countDownLatch(LATCH_IS_ON_CALL_REMOVED);
+        }
+
+        @Override
+        public void onCallStateChanged(Call call, int state) {
+            Log.i(LOG_TAG, "onCallStateChanged " + state + "Call: " + call);
+
+            switch(state) {
+                case Call.STATE_DIALING : {
+                    countDownLatch(LATCH_IS_CALL_DIALING);
+                    break;
+                }
+                case Call.STATE_ACTIVE : {
+                    countDownLatch(LATCH_IS_CALL_ACTIVE);
+                    break;
+                }
+                case Call.STATE_DISCONNECTING : {
+                    countDownLatch(LATCH_IS_CALL_DISCONNECTING);
+                    break;
+                }
+                case Call.STATE_DISCONNECTED : {
+                    countDownLatch(LATCH_IS_CALL_DISCONNECTED);
+                    break;
+                }
+                case Call.STATE_RINGING : {
+                    countDownLatch(LATCH_IS_CALL_RINGING);
+                    break;
+                }
+                case Call.STATE_HOLDING : {
+                    countDownLatch(LATCH_IS_CALL_HOLDING);
+                    break;
+                }
+                default:
+                    break;
+            }
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getInstrumentation().getContext();
+    }
+
+    private static String setDefaultDialer(Instrumentation instrumentation, String packageName)
+            throws Exception {
+        String str =  TelephonyUtils.executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER
+                + packageName);
+        return str;
+    }
+
+    private static String getDefaultDialer(Instrumentation instrumentation) throws Exception {
+        String str = TelephonyUtils.executeShellCommand(instrumentation,
+                COMMAND_GET_DEFAULT_DIALER);
+        return str;
+    }
+
+    private static void overrideCarrierConfig(PersistableBundle bundle) throws Exception {
+        CarrierConfigManager carrierConfigManager = InstrumentationRegistry.getInstrumentation()
+                .getContext().getSystemService(CarrierConfigManager.class);
+        sReceiver.clearQueue();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(carrierConfigManager,
+                (m) -> m.overrideConfig(sTestSub, bundle));
+        sReceiver.waitForChanged();
+    }
+
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
index ae6306a..a994d98 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceConnector.java
@@ -81,6 +81,12 @@
     private static final String COMMAND_SET_TEST_MODE_ENABLED = "src set-test-enabled ";
     private static final String COMMAND_SET_D2D_ENABLED = "d2d set-device-support ";
 
+    private boolean mIsTestTypeExecutor = false;
+
+    public void setExecutorTestType(boolean type) {
+        mIsTestTypeExecutor = type;
+    }
+
     private class TestCarrierServiceConnection implements ServiceConnection {
 
         private final CountDownLatch mLatch;
@@ -117,7 +123,7 @@
 
         @Override
         public void onServiceDisconnected(ComponentName name) {
-            mCarrierService = null;
+            mExternalService = null;
         }
     }
 
@@ -143,11 +149,13 @@
             mIsServiceOverridden = true;
             switch (mConnectionType) {
                 case CONNECTION_TYPE_IMS_SERVICE_CARRIER: {
-                    setCarrierImsService("none");
+                    boolean unbindSent = setCarrierImsService("none");
+                    if (unbindSent) waitForCarrierPackageUnbind();
                     break;
                 }
                 case CONNECTION_TYPE_IMS_SERVICE_DEVICE: {
-                    setDeviceImsService("");
+                    boolean unbindSent = setDeviceImsService("");
+                    if (unbindSent) waitForDevicePackageUnbind();
                     break;
                 }
                 case CONNECTION_TYPE_DEFAULT_SMS_APP: {
@@ -157,6 +165,35 @@
             }
         }
 
+        void waitForCarrierPackageUnbind() {
+            TestImsService carrierService = getCarrierService();
+            if (carrierService == null) return;
+            // First unbind the local services
+            removeLocalCarrierServiceConnection();
+            // Then wait for AOSP to unbind if there is still an active binding.
+            boolean isBound = carrierService.isTelephonyBound();
+            if (ImsUtils.VDBG) Log.i(TAG, "waitForCarrierPackageUnbind: isBound=" + isBound);
+            if (isBound) {
+                // Wait for telephony to unbind to local ImsService
+                carrierService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+            }
+        }
+
+        void waitForDevicePackageUnbind() throws Exception {
+            // Wait until the ImsService unbinds
+            ITestExternalImsService externalService = getExternalService();
+            if (externalService == null) return;
+            // First unbind the local services
+            removeLocalExternalServiceConnection();
+            // Then wait for AOSP to unbind if there is still an active binding.
+            boolean isBound = externalService.isTelephonyBound();
+            if (ImsUtils.VDBG) Log.i(TAG, "waitForDevicePackageUnbind: isBound=" + isBound);
+            if (isBound) {
+                // Wait for telephony to unbind to external ImsService
+                externalService.waitForLatchCountdown(TestImsService.LATCH_ON_UNBIND);
+            }
+        }
+
         boolean overrideService(ImsFeatureConfiguration config) throws Exception {
             mIsServiceOverridden = true;
             switch (mConnectionType) {
@@ -480,6 +517,11 @@
             return false;
         }
         mCarrierService.resetState();
+        if (mIsTestTypeExecutor) {
+            mCarrierService.setExecutorTestType(mIsTestTypeExecutor);
+            // reset the mIsTestTypeExecutor value
+            mIsTestTypeExecutor = false;
+        }
         return true;
     }
 
@@ -559,17 +601,27 @@
         }
     }
 
-    // Detect and disconnect all active services.
-    void disconnectServices() throws Exception {
-        // Remove local connection
+    void removeLocalCarrierServiceConnection() {
         if (mCarrierServiceConn != null) {
             mInstrumentation.getContext().unbindService(mCarrierServiceConn);
+            mCarrierServiceConn = null;
             mCarrierService = null;
         }
+    }
+
+    void removeLocalExternalServiceConnection() {
         if (mExternalServiceConn != null) {
             mInstrumentation.getContext().unbindService(mExternalServiceConn);
+            mExternalServiceConn = null;
             mExternalService = null;
         }
+    }
+
+    // Detect and disconnect all active services.
+    void disconnectServices() throws Exception {
+        // Remove local connections
+        removeLocalCarrierServiceConnection();
+        removeLocalExternalServiceConnection();
         mDeviceServiceConnection.restoreOriginalPackage();
         mCarrierServiceConnection.restoreOriginalPackage();
         mDefaultSmsAppConnection.restoreOriginalPackage();
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 3c1e614..09ea8d8 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
@@ -41,9 +41,9 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.telephony.cts.AsyncSmsMessageListener;
+import android.telephony.cts.CarrierCapability;
 import android.telephony.cts.SmsReceiverHelper;
 import android.telephony.cts.TelephonyUtils;
-import android.telephony.cts.CarrierCapability;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
 import android.telephony.ims.ImsMmTelManager;
@@ -87,14 +87,13 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
-import android.util.Log;
-
 /**
  * CTS tests for ImsService API.
  */
@@ -366,7 +365,7 @@
         if (!ImsUtils.shouldTestImsService()) {
             return;
         }
-        // Unbind the GTS ImsService after the test completes.
+        // Unbind the ImsService after the test completes.
         if (sServiceConnector != null) {
             sServiceConnector.setSingleRegistrationTestModeEnabled(false);
             sServiceConnector.disconnectCarrierImsService();
@@ -389,6 +388,26 @@
                 TestImsService.LATCH_CREATE_RCS);
         assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
                 sServiceConnector.getCarrierService().getRcsFeature());
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+    }
+
+    @Test
+    public void testCarrierImsServiceBindRcsFeatureForExecutor() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        sServiceConnector.setExecutorTestType(true);
+        // Connect to the ImsService with the RCS feature.
+        assertTrue(sServiceConnector.connectCarrierImsService(new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+                .build()));
+        // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_RCS);
+        assertNotNull("ImsService created, but ImsService#createRcsFeature was not called!",
+                sServiceConnector.getCarrierService().getRcsFeature());
     }
 
     @Test
@@ -409,6 +428,8 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -438,6 +459,8 @@
         assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_DISABLE_IMS));
         assertFalse(sServiceConnector.getCarrierService().isEnabled());
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -466,6 +489,41 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
+    }
+
+    @Test
+    public void testCarrierImsServiceBindRcsChangeToMmtelCompat() throws Exception {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+        // Connect to the ImsService with the RCS feature.
+        ImsFeatureConfiguration config = new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_RCS)
+                .build();
+        assertTrue(sServiceConnector.connectCarrierImsServiceLocally());
+        sServiceConnector.getCarrierService().resetState();
+        // Set the flag for ImsService compatibility test.
+        sServiceConnector.getCarrierService().setImsServiceCompat();
+        assertTrue(sServiceConnector.triggerFrameworkConnectionToCarrierImsService(config));
+        // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
+        // Framework did not call it.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_RCS));
+
+        // Change the supported feature to MMTEl
+        sServiceConnector.getCarrierService().getImsServiceCompat().onUpdateSupportedImsFeatures(
+                new ImsFeatureConfiguration.Builder()
+                .addFeature(sTestSlot, ImsFeature.FEATURE_MMTEL).build());
+
+        // createMmTelFeature should be called.
+        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_CREATE_MMTEL));
+
+        // Wait for the framework to set the capabilities on the ImsService
+        sServiceConnector.getCarrierService().waitForLatchCountdown(
+                TestImsService.LATCH_MMTEL_CAP_SET);
     }
 
     @Test
@@ -503,6 +561,8 @@
         // Wait for the framework to set the capabilities on the ImsService
         sServiceConnector.getCarrierService().waitForLatchCountdown(
                 TestImsService.LATCH_MMTEL_CAP_SET);
+        assertTrue("Not expected subId received!",
+                isExpectedSubId(sServiceConnector.getCarrierService().getSubIDs()));
     }
 
     @Test
@@ -4378,4 +4438,8 @@
 
         throw new RuntimeException("Invalid hex char '" + c + "'");
     }
+
+    private boolean isExpectedSubId(HashSet<Integer> subIDs) {
+        return (subIDs.size() == 1) && subIDs.contains(sTestSub);
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
index 82a2f2a..b53d13e 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsUtils.java
@@ -59,18 +59,13 @@
     public static boolean shouldTestImsService() {
         final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
                 .getPackageManager();
-        boolean hasTelephony = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY);
-        boolean hasIms = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
-        return hasTelephony && hasIms;
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
     }
 
     public static boolean shouldTestImsSingleRegistration() {
         final PackageManager pm = InstrumentationRegistry.getInstrumentation().getContext()
                 .getPackageManager();
-        boolean hasIms = pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS);
-        boolean hasSingleReg = pm.hasSystemFeature(
-                PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
-        return hasIms && hasSingleReg;
+        return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS_SINGLE_REGISTRATION);
     }
 
     public static int getPreferredActiveSubId() {
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 d4386ff..8f4a4104 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
@@ -1135,6 +1135,149 @@
         overrideCarrierConfig(null);
     }
 
+    /**
+     * Tests the case when contact1 has had a successful network query, but a query for contact2
+     * has resulted in the carrier network not responding with a NOTIFY. If contact1 caps are
+     * queried again, the query to the cache for contact1 should not be blocked in a queue behind
+     * the pending network query. Eventually, request for contact2 will timeout with onTimeout
+     * response from vendor, which will result in ERROR_REQUEST_TIMEOUT result back to app.
+     */
+    @Test
+    public void testCacheQuerySuccessWhenNetworkBlocked() 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, true /* presence cap */, false /* OPTIONS */);
+
+        TestRcsCapabilityExchangeImpl capabilityExchangeImpl = sServiceConnector
+                .getCarrierService().getRcsFeature().getRcsCapabilityExchangeImpl();
+
+        BlockingQueue<Boolean> completeQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<RcsContactUceCapability> capabilityQueue = new LinkedBlockingQueue<>();
+        BlockingQueue<Integer> errorQueue = new LinkedBlockingQueue<>();
+        RcsUceAdapter.CapabilitiesCallback callback = new RcsUceAdapter.CapabilitiesCallback() {
+            @Override
+            public void onCapabilitiesReceived(List<RcsContactUceCapability> capabilities) {
+                capabilities.forEach(capabilityQueue::offer);
+            }
+            @Override
+            public void onComplete() {
+                completeQueue.offer(true);
+            }
+            @Override
+            public void onError(int errorCode, long retryAfterMilliseconds) {
+                errorQueue.offer(errorCode);
+            }
+        };
+
+        // Prepare two contacts
+        final Uri contact1 = sTestNumberUri;
+        final Uri contact2 = sTestContact2Uri;
+
+        Collection<Uri> contacts = new ArrayList<>(1);
+        contacts.add(contact1);
+
+        ArrayList<String> pidfXmlList = new ArrayList<>(1);
+        pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+        // Setup the network response is 200 OK and notify capabilities update
+        int networkRespCode = 200;
+        String networkRespReason = "OK";
+        capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+            cb.onNetworkResponse(networkRespCode, networkRespReason);
+            cb.onNotifyCapabilitiesUpdate(pidfXmlList);
+            cb.onTerminated("", 0L);
+        });
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        List<RcsContactUceCapability> resultCapList = new ArrayList<>();
+
+        // Verify that the first contact is updated
+        RcsContactUceCapability capability = waitForResult(capabilityQueue);
+        assertNotNull("Cannot receive the first capabilities result.", capability);
+        resultCapList.add(capability);
+
+        // Verify contact1's capabilities from the received capabilities list
+        RcsContactUceCapability resultCapability = getContactCapability(resultCapList, contact1);
+        assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_NETWORK,
+                REQUEST_RESULT_FOUND, true, true);
+
+        // Verify the onCompleted is called
+        waitForResult(completeQueue);
+
+        completeQueue.clear();
+        capabilityQueue.clear();
+        resultCapList.clear();
+
+        // Now hold the second contact and do not return a response until after contact1 is queried
+        //again
+        CountDownLatch latch = new CountDownLatch(1);
+        capabilityExchangeImpl.setSubscribeOperation((uris, cb) -> {
+            try {
+                cb.onNetworkResponse(networkRespCode, networkRespReason);
+                assertTrue("Timed out waiting for latch", latch.await(10, TimeUnit.SECONDS));
+                // We didn't receive any NOTIFY
+                cb.onTerminated("timeout", 0L);
+            } catch (InterruptedException e) {
+                fail("Waiting for cap response resulted in unexpected exception: " + e);
+            }
+        });
+
+        contacts.clear();
+        contacts.add(contact2);
+
+        pidfXmlList.clear();
+        pidfXmlList.add(getPidfXmlData(contact2, true, true));
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        // Send another request for contact1's caps. Although the request queue is blocked due to
+        // pending network request, contact1 has valid caps, so system should return those.
+        contacts.clear();
+        contacts.add(contact1);
+
+        pidfXmlList.clear();
+        pidfXmlList.add(getPidfXmlData(contact1, true, true));
+
+        requestCapabilities(uceAdapter, contacts, callback);
+
+        capability = waitForResult(capabilityQueue);
+        assertNotNull("Cannot receive the cached capabilities result.", capability);
+        resultCapList.add(capability);
+
+        // Verify contact1's capabilities from the received capabilities list
+        resultCapability = getContactCapability(resultCapList, contact1);
+        assertNotNull("Cannot find the contact: " + contact1, resultCapability);
+        verifyCapabilityResult(resultCapability, contact1, SOURCE_TYPE_CACHED, REQUEST_RESULT_FOUND,
+                true, true);
+
+        // Now contact2's query finishes and it timed out without a NOTIFY
+        latch.countDown();
+
+        Integer error = waitForResult(errorQueue);
+        assertNotNull("Cannot receive the expected error result.", capability);
+        assertEquals("Timeout without NOTIFY should result in ERROR_REQUEST_TIMEOUT",
+                RcsUceAdapter.ERROR_REQUEST_TIMEOUT, error.intValue());
+
+        errorQueue.clear();
+        completeQueue.clear();
+        capabilityQueue.clear();
+        resultCapList.clear();
+        removeTestContactFromEab();
+
+        overrideCarrierConfig(null);
+    }
+
     @Test
     public void testRequestCapabilitiesFromCacheWithPresenceMechanism() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
new file mode 100644
index 0000000..1dba231
--- /dev/null
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsCallSessionImpl.java
@@ -0,0 +1,535 @@
+/*
+ * 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 static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsCallSessionListener;
+import android.telephony.ims.ImsReasonInfo;
+import android.telephony.ims.ImsStreamMediaProfile;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+public class TestImsCallSessionImpl extends ImsCallSessionImplBase {
+
+    private static final String LOG_TAG = "CtsTestImsCallSessionImpl";
+    private static final int LATCH_WAIT = 0;
+    private static final int LATCH_MAX = 1;
+    private static final int WAIT_FOR_STATE_CHANGE = 2000;
+    private static final int WAIT_FOR_ESTABLISHING = 5000;
+
+    private final String mCallId = String.valueOf(this.hashCode());
+    private final Object mLock = new Object();
+
+    private int mState = ImsCallSessionImplBase.State.IDLE;
+    private ImsCallProfile mCallProfile;
+    private ImsCallProfile mLocalCallProfile;
+    private ImsCallSessionListener mListener;
+
+    private final MessageExecutor mCallExecutor = new MessageExecutor("CallExecutor");
+    private final MessageExecutor mCallBackExecutor = new MessageExecutor("CallBackExecutor");
+    private static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
+    static {
+        for (int i = 0; i < LATCH_MAX; i++) {
+            sLatches[i] = new CountDownLatch(1);
+        }
+    }
+
+    public static final int TEST_TYPE_NONE = 0x00000000;
+    public static final int TEST_TYPE_MO_ANSWER = 0x00000001;
+    public static final int TEST_TYPE_MO_FAILED = 0x00000002;
+    public static final int TEST_TYPE_HOLD_FAILED = 0x00000004;
+    public static final int TEST_TYPE_RESUME_FAILED = 0x00000008;
+
+    private int mTestType = TEST_TYPE_NONE;
+
+    public boolean imsCallSessionLatchCountdown(int latchIndex, int waitMs) {
+        boolean complete = false;
+        try {
+            CountDownLatch latch;
+            synchronized (mLock) {
+                latch = sLatches[latchIndex];
+            }
+            complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+             //complete == false
+        }
+        synchronized (mLock) {
+            sLatches[latchIndex] = new CountDownLatch(1);
+        }
+        return complete;
+    }
+
+    public void countDownLatch(int latchIndex) {
+        synchronized (mLock) {
+            sLatches[latchIndex].countDown();
+        }
+    }
+
+    public TestImsCallSessionImpl(ImsCallProfile profile) {
+        mCallProfile = profile;
+    }
+
+    @Override
+    public String getCallId() {
+        return mCallId;
+    }
+
+    @Override
+    public ImsCallProfile getCallProfile() {
+        return mCallProfile;
+    }
+
+    @Override
+    public ImsCallProfile getLocalCallProfile() {
+        return mLocalCallProfile;
+    }
+
+    @Override
+    public int getState() {
+        return mState;
+    }
+
+    @Override
+    public boolean isInCall() {
+        return (mState == ImsCallSessionImplBase.State.ESTABLISHED) ? true : false;
+    }
+
+    @Override
+    public void setListener(ImsCallSessionListener listener) {
+        mListener = listener;
+    }
+
+    @Override
+    public void start(String callee, ImsCallProfile profile) {
+        mLocalCallProfile = profile;
+        int state = getState();
+
+        if ((state != ImsCallSessionImplBase.State.IDLE)
+                || (state != ImsCallSessionImplBase.State.INITIATED)) {
+            Log.d(LOG_TAG, "start :: Illegal state; callId= " + getCallId()
+                    + ", state=" + getState());
+        }
+
+        mCallExecutor.execute(() -> {
+            imsCallSessionLatchCountdown(LATCH_WAIT, 500);
+            if (isTestType(TEST_TYPE_MO_FAILED)) {
+                startFailed();
+            } else {
+                startInternal();
+            }
+        });
+    }
+
+    void startInternal() {
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeInitiating mCallId = " + mCallId);
+                mListener.callSessionInitiating(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.INITIATED);
+
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+        ImsCallProfile profile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+        mCallProfile.updateMediaProfile(profile);
+
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeProgressing mCallId = " + mCallId);
+                mListener.callSessionProgressing(mCallProfile.getMediaProfile());
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHING);
+
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+                mListener.callSessionInitiated(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHED);
+    }
+
+    void startFailed() {
+        imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_STATE_CHANGE);
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokestartFailed mCallId = " + mCallId);
+                mListener.callSessionInitiatingFailed(getReasonInfo(
+                        ImsReasonInfo.CODE_LOCAL_INTERNAL_ERROR, ImsReasonInfo.CODE_UNSPECIFIED));
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.TERMINATED);
+    }
+
+    @Override
+    public void accept(int callType, ImsStreamMediaProfile profile) {
+        Log.i(LOG_TAG, "Accept Call");
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeStarted mCallId = " + mCallId);
+                mListener.callSessionInitiated(mCallProfile);
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.ESTABLISHED);
+    }
+
+    @Override
+    public void reject(int reason) {
+        int state = getState();
+        if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                    mListener.callSessionTerminated(getReasonInfo(
+                            ImsReasonInfo.CODE_USER_TERMINATED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.TERMINATED);
+        }
+    }
+
+    @Override
+    public void terminate(int reason) {
+        int state = getState();
+        postAndRunTask(() -> {
+            try {
+                if (mListener == null) {
+                    return;
+                }
+                Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                mListener.callSessionTerminated(getReasonInfo(ImsReasonInfo.CODE_USER_TERMINATED,
+                        ImsReasonInfo.CODE_UNSPECIFIED));
+            } catch (Throwable t) {
+                Throwable cause = t.getCause();
+                if (t instanceof DeadObjectException
+                        || (cause != null && cause instanceof DeadObjectException)) {
+                    fail("starting cause Throwable to be thrown: " + t);
+                }
+            }
+        });
+        setState(ImsCallSessionImplBase.State.TERMINATED);
+    }
+
+    // End the Incoming Call from local side after accept.
+    public void terminateIncomingCall() {
+        int state = getState();
+        if (state == ImsCallSessionImplBase.State.ESTABLISHED) {
+            postAndRunTask(() -> {
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeTerminated mCallId = " + mCallId);
+                    mListener.callSessionTerminated(getReasonInfo(
+                            ImsReasonInfo.CODE_USER_TERMINATED_BY_REMOTE,
+                            ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.TERMINATED);
+        }
+    }
+
+    @Override
+    public void hold(ImsStreamMediaProfile profile) {
+        if (isTestType(TEST_TYPE_HOLD_FAILED)) {
+            holdFailed(profile);
+        } else {
+            int audioDirection = profile.getAudioDirection();
+            if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+                ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                        ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                        ImsStreamMediaProfile.DIRECTION_RECEIVE,
+                        ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                        ImsStreamMediaProfile.DIRECTION_INVALID,
+                        ImsStreamMediaProfile.RTT_MODE_DISABLED);
+                ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                        ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+                mCallProfile.updateMediaProfile(mprofile);
+            }
+            setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeHeld mCallId = " + mCallId);
+                    mListener.callSessionHeld(mCallProfile);
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    @Override
+    public void resume(ImsStreamMediaProfile profile) {
+        if (isTestType(TEST_TYPE_RESUME_FAILED)) {
+            resumeFailed(profile);
+        } else {
+            int audioDirection = profile.getAudioDirection();
+            if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+                ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                        ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                        ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                        ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                        ImsStreamMediaProfile.DIRECTION_INVALID,
+                        ImsStreamMediaProfile.RTT_MODE_DISABLED);
+                ImsCallProfile mprofile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                        ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+                mCallProfile.updateMediaProfile(mprofile);
+            }
+            setState(ImsCallSessionImplBase.State.RENEGOTIATING);
+
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeResume mCallId = " + mCallId);
+                    mListener.callSessionResumed(mCallProfile);
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    private void holdFailed(ImsStreamMediaProfile profile) {
+        int audioDirection = profile.getAudioDirection();
+        if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND) {
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeHoldFailed mCallId = " + mCallId);
+                    mListener.callSessionHoldFailed(getReasonInfo(ImsReasonInfo
+                            .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    private void resumeFailed(ImsStreamMediaProfile profile) {
+        int audioDirection = profile.getAudioDirection();
+        if (audioDirection == ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE) {
+            postAndRunTask(() -> {
+                imsCallSessionLatchCountdown(LATCH_WAIT, WAIT_FOR_ESTABLISHING);
+                try {
+                    if (mListener == null) {
+                        return;
+                    }
+                    Log.d(LOG_TAG, "invokeResumeFailed mCallId = " + mCallId);
+                    mListener.callSessionResumeFailed(getReasonInfo(ImsReasonInfo
+                            .CODE_SESSION_MODIFICATION_FAILED, ImsReasonInfo.CODE_UNSPECIFIED));
+                } catch (Throwable t) {
+                    Throwable cause = t.getCause();
+                    if (t instanceof DeadObjectException
+                            || (cause != null && cause instanceof DeadObjectException)) {
+                        fail("starting cause Throwable to be thrown: " + t);
+                    }
+                }
+            });
+            setState(ImsCallSessionImplBase.State.ESTABLISHED);
+        }
+    }
+
+    private void setState(int state) {
+        if (mState != state) {
+            Log.d(LOG_TAG, "ImsCallSession :: " + mState + " >> " + state);
+            mState = state;
+        }
+    }
+
+    public boolean isInTerminated() {
+        return (mState == ImsCallSessionImplBase.State.TERMINATED) ? true : false;
+    }
+
+    private ImsReasonInfo getReasonInfo(int code, int extraCode) {
+        ImsReasonInfo reasonInfo = new ImsReasonInfo(code, extraCode, "");
+        return reasonInfo;
+    }
+
+    public void addTestType(int type) {
+        mTestType |= type;
+    }
+
+    public void removeTestType(int type) {
+        mTestType &= ~type;
+    }
+
+    public boolean isTestType(int type) {
+        return  ((mTestType & type) == type);
+    }
+
+    public Executor getExecutor() {
+        return mCallBackExecutor;
+    }
+
+    private void postAndRunTask(Runnable task) {
+        mCallBackExecutor.execute(task);
+    }
+
+    private static Looper createLooper(String name) {
+        HandlerThread thread = new HandlerThread(name);
+        thread.start();
+
+        Looper looper = thread.getLooper();
+
+        if (looper == null) {
+            return Looper.getMainLooper();
+        }
+        return looper;
+    }
+     /**
+     * Executes the tasks in the other thread rather than the calling thread.
+     */
+    public class MessageExecutor extends Handler implements Executor {
+        public MessageExecutor(String name) {
+            super(createLooper(name));
+        }
+
+        @Override
+        public void execute(Runnable r) {
+            Message m = Message.obtain(this, 0 /* don't care */, r);
+            m.sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.obj instanceof Runnable) {
+                executeInternal((Runnable) msg.obj);
+            } else {
+                Log.d(LOG_TAG, "[MessageExecutor] handleMessage :: "
+                        + "Not runnable object; ignore the msg=" + msg);
+            }
+        }
+
+        private void executeInternal(Runnable r) {
+            try {
+                r.run();
+            } catch (Throwable t) {
+                Log.d(LOG_TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+                t.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
index 10a39d3..3e3b331 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsConfig.java
@@ -18,15 +18,25 @@
 
 import android.telephony.ims.RcsClientConfiguration;
 import android.telephony.ims.stub.ImsConfigImplBase;
+import android.util.Log;
 
 import java.util.HashMap;
+import java.util.concurrent.Executor;
 
 public class TestImsConfig extends ImsConfigImplBase {
 
+    private static final String TAG = "TestImsConfig";
     private HashMap<Integer, Integer> mIntHashMap = new HashMap<>();
     private HashMap<Integer, String> mStringHashMap = new HashMap<>();
 
     TestImsConfig() {
+        Log.d(TAG, "TestImsConfig with default constructor");
+        TestAcsClient.getInstance().setImsConfigImpl(this);
+    }
+
+    TestImsConfig(Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestImsConfig with Executor constructor");
         TestAcsClient.getInstance().setImsConfigImpl(this);
     }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
index afdd3d1..cb3a4d9 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsRegistration.java
@@ -17,13 +17,26 @@
 package android.telephony.ims.cts;
 
 import android.telephony.ims.stub.ImsRegistrationImplBase;
+import android.util.Log;
 
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
 public class TestImsRegistration extends ImsRegistrationImplBase {
 
+    private static final String TAG = "TestImsRegistration";
+
+    public TestImsRegistration() {
+        Log.d(TAG, "TestImsRegistration with default constructor");
+    }
+
+    public TestImsRegistration(Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestImsRegistration with Executor constructor");
+    }
+
     public static class NetworkRegistrationInfo {
         public final int sipCode;
         public final String sipReason;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
index a012513..97fa8f2 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestImsService.java
@@ -20,7 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.telephony.ims.ImsService;
 import android.telephony.ims.feature.MmTelFeature;
 import android.telephony.ims.feature.RcsFeature;
@@ -30,10 +34,12 @@
 import android.telephony.ims.stub.SipTransportImplBase;
 import android.util.Log;
 
-
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import java.util.HashSet;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -41,22 +47,27 @@
  */
 public class TestImsService extends Service {
 
-    private static final String TAG = "GtsImsTestImsService";
+    private static final String TAG = "CtsImsTestImsService";
+    private static MessageExecutor sMessageExecutor = null;
 
-    private static final TestImsRegistration sImsRegistrationImplBase =
-            new TestImsRegistration();
-
+    private TestImsRegistration mImsRegistrationImplBase;
     private TestRcsFeature mTestRcsFeature;
     private TestMmTelFeature mTestMmTelFeature;
     private TestImsConfig mTestImsConfig;
     private TestSipTransport mTestSipTransport;
     private ImsService mTestImsService;
+    private ImsService mTestImsServiceCompat;
+    private Executor mExecutor = Runnable::run;
     private boolean mIsEnabled = false;
     private boolean mSetNullRcsBinding = false;
     private boolean mIsSipTransportImplemented = false;
+    private boolean mIsTestTypeExecutor = false;
+    private boolean mIsImsServiceCompat = false;
     private long mCapabilities = 0;
     private ImsFeatureConfiguration mFeatureConfig;
-    private final Object mLock = new Object();
+    protected boolean mIsTelephonyBound = false;
+    private HashSet<Integer> mSubIDs = new HashSet<Integer>();
+    protected final Object mLock = new Object();
 
     public static final int LATCH_FEATURES_READY = 0;
     public static final int LATCH_ENABLE_IMS = 1;
@@ -71,7 +82,8 @@
     public static final int LATCH_RCS_CAP_SET = 10;
     public static final int LATCH_UCE_LISTENER_SET = 11;
     public static final int LATCH_UCE_REQUEST_PUBLISH = 12;
-    private static final int LATCH_MAX = 13;
+    public static final int LATCH_ON_UNBIND = 13;
+    private static final int LATCH_MAX = 14;
     protected static final CountDownLatch[] sLatches = new CountDownLatch[LATCH_MAX];
     static {
         for (int i = 0; i < LATCH_MAX; i++) {
@@ -106,9 +118,193 @@
             if (getBaseContext() == null) {
                 attachBaseContext(context);
             }
-            mTestImsConfig = new TestImsConfig();
-            // For testing, just run on binder thread until required otherwise.
-            mTestSipTransport = new TestSipTransport(Runnable::run);
+
+            if (mIsTestTypeExecutor) {
+                mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+                mTestSipTransport = new TestSipTransport(mExecutor);
+                mTestImsConfig = new TestImsConfig(mExecutor);
+            } else {
+                mImsRegistrationImplBase = new TestImsRegistration();
+                mTestImsConfig = new TestImsConfig();
+                mTestSipTransport = new TestSipTransport();
+            }
+        }
+
+        @Override
+        public ImsFeatureConfiguration querySupportedImsFeatures() {
+            return getFeatureConfig();
+        }
+
+        @Override
+        public long getImsServiceCapabilities() {
+            return mCapabilities;
+        }
+
+        @Override
+        public void readyForFeatureCreation() {
+            synchronized (mLock) {
+                countDownLatch(LATCH_FEATURES_READY);
+            }
+        }
+
+        @Override
+        public void enableImsForSubscription(int slotId, int subId) {
+            synchronized (mLock) {
+                countDownLatch(LATCH_ENABLE_IMS);
+                mSubIDs.add(subId);
+                setIsEnabled(true);
+            }
+        }
+
+        @Override
+        public void disableImsForSubscription(int slotId, int subId) {
+            synchronized (mLock) {
+                countDownLatch(LATCH_DISABLE_IMS);
+                mSubIDs.add(subId);
+                setIsEnabled(false);
+            }
+        }
+
+        @Override
+        public RcsFeature createRcsFeatureForSubscription(int slotId, int subId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_RCS);
+                    mTestRcsFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener setListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_CAP_SET);
+                }
+            };
+
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_UCE_LISTENER_SET);
+                }
+            };
+
+            synchronized (mLock) {
+                countDownLatch(LATCH_CREATE_RCS);
+                mSubIDs.add(subId);
+
+                if (mIsTestTypeExecutor) {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener, mExecutor);
+                } else {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener);
+                }
+
+                // Setup UCE request listener
+                mTestRcsFeature.setDeviceCapPublishListener(() -> {
+                    synchronized (mLock) {
+                        countDownLatch(LATCH_UCE_REQUEST_PUBLISH);
+                    }
+                });
+
+                if (mSetNullRcsBinding) {
+                    return null;
+                }
+                return mTestRcsFeature;
+            }
+        }
+
+        @Override
+        public ImsConfigImplBase getConfigForSubscription(int slotId, int subId) {
+            mSubIDs.add(subId);
+            return mTestImsConfig;
+        }
+
+        @Override
+        public MmTelFeature createMmTelFeatureForSubscription(int slotId, int subId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_MMTEL);
+                    mTestMmTelFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener capSetListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_CAP_SET);
+                }
+            };
+
+            synchronized (mLock) {
+                countDownLatch(LATCH_CREATE_MMTEL);
+                mSubIDs.add(subId);
+                if (mIsTestTypeExecutor) {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener, mExecutor);
+                } else {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener);
+                }
+
+                return mTestMmTelFeature;
+            }
+        }
+
+        @Override
+        public ImsRegistrationImplBase getRegistrationForSubscription(int slotId, int subId) {
+            mSubIDs.add(subId);
+            return mImsRegistrationImplBase;
+        }
+
+        @Nullable
+        @Override
+        public SipTransportImplBase getSipTransport(int slotId) {
+            if (mIsSipTransportImplemented) {
+                return mTestSipTransport;
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        public @NonNull Executor getExecutor() {
+            if (mIsTestTypeExecutor) {
+                return mExecutor;
+            } else {
+                mExecutor = Runnable::run;
+                return mExecutor;
+            }
+        }
+    }
+
+    private class ImsServiceUT_compat extends ImsService {
+
+        ImsServiceUT_compat(Context context) {
+            // As explained above, ImsServiceUT is created in order to get around classloader
+            // restrictions. Attach the base context from the wrapper ImsService.
+            if (getBaseContext() == null) {
+                attachBaseContext(context);
+            }
+
+            if (mIsTestTypeExecutor) {
+                mImsRegistrationImplBase = new TestImsRegistration(mExecutor);
+                mTestSipTransport = new TestSipTransport(mExecutor);
+                mTestImsConfig = new TestImsConfig(mExecutor);
+            } else {
+                mImsRegistrationImplBase = new TestImsRegistration();
+                mTestImsConfig = new TestImsConfig();
+                mTestSipTransport = new TestSipTransport();
+            }
         }
 
         @Override
@@ -146,33 +342,42 @@
 
         @Override
         public RcsFeature createRcsFeature(int slotId) {
+
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_RCS);
+                    mTestRcsFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener setListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_RCS_CAP_SET);
+                }
+            };
+
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_UCE_LISTENER_SET);
+                }
+            };
+
             synchronized (mLock) {
                 countDownLatch(LATCH_CREATE_RCS);
-                mTestRcsFeature = new TestRcsFeature(getBaseContext(),
-                        //onReady
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_RCS_READY);
-                            }
-                        },
-                        //onRemoved
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_REMOVE_RCS);
-                                mTestRcsFeature = null;
-                            }
-                        },
-                        //onCapabilitiesSet
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_RCS_CAP_SET);
-                            }
-                        },
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_UCE_LISTENER_SET);
-                        }
-                        });
+
+                if (mIsTestTypeExecutor) {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener, mExecutor);
+                } else {
+                    mTestRcsFeature = new TestRcsFeature(readyListener, removedListener,
+                            setListener, capExchangeEventListener);
+                }
 
                 // Setup UCE request listener
                 mTestRcsFeature.setDeviceCapPublishListener(() -> {
@@ -195,36 +400,42 @@
 
         @Override
         public MmTelFeature createMmTelFeature(int slotId) {
+            TestImsService.ReadyListener readyListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_READY);
+                }
+            };
+
+            TestImsService.RemovedListener removedListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_REMOVE_MMTEL);
+                    mTestMmTelFeature = null;
+                }
+            };
+
+            TestImsService.CapabilitiesSetListener capSetListener = () -> {
+                synchronized (mLock) {
+                    countDownLatch(LATCH_MMTEL_CAP_SET);
+                }
+            };
+
             synchronized (mLock) {
                 countDownLatch(LATCH_CREATE_MMTEL);
-                mTestMmTelFeature = new TestMmTelFeature(
-                        //onReady
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_MMTEL_READY);
-                            }
-                        },
-                        //onRemoved
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_REMOVE_MMTEL);
-                                mTestMmTelFeature = null;
-                            }
-                        },
-                        //onCapabilitiesSet
-                        () -> {
-                            synchronized (mLock) {
-                                countDownLatch(LATCH_MMTEL_CAP_SET);
-                            }
-                        }
-                        );
+                if (mIsTestTypeExecutor) {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener, mExecutor);
+                } else {
+                    mTestMmTelFeature = new TestMmTelFeature(readyListener, removedListener,
+                            capSetListener);
+                }
+
                 return mTestMmTelFeature;
             }
         }
 
         @Override
         public ImsRegistrationImplBase getRegistration(int slotId) {
-            return sImsRegistrationImplBase;
+            return mImsRegistrationImplBase;
         }
 
         @Nullable
@@ -236,6 +447,62 @@
                 return null;
             }
         }
+
+        @Override
+        public @NonNull Executor getExecutor() {
+            if (mIsTestTypeExecutor) {
+                return mExecutor;
+            } else {
+                mExecutor = Runnable::run;
+                return mExecutor;
+            }
+        }
+    }
+
+    private static Looper createLooper(String name) {
+        HandlerThread thread = new HandlerThread(name);
+        thread.start();
+
+        Looper looper = thread.getLooper();
+
+        if (looper == null) {
+            return Looper.getMainLooper();
+        }
+        return looper;
+    }
+
+    /**
+     * Executes the tasks in the other thread rather than the calling thread.
+     */
+    public class MessageExecutor extends Handler implements Executor {
+        public MessageExecutor(String name) {
+            super(createLooper(name));
+        }
+
+        @Override
+        public void execute(Runnable r) {
+            Message m = Message.obtain(this, 0, r);
+            m.sendToTarget();
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.obj instanceof Runnable) {
+                executeInternal((Runnable) msg.obj);
+            } else {
+                Log.d(TAG, "[MessageExecutor] handleMessage :: "
+                        + "Not runnable object; ignore the msg=" + msg);
+            }
+        }
+
+        private void executeInternal(Runnable r) {
+            try {
+                r.run();
+            } catch (Throwable t) {
+                Log.d(TAG, "[MessageExecutor] executeInternal :: run task=" + r);
+                t.printStackTrace();
+            }
+        }
     }
 
     private final LocalBinder mBinder = new LocalBinder();
@@ -256,18 +523,53 @@
         }
     }
 
+    protected ImsService getImsServiceCompat() {
+        synchronized (mLock) {
+            if (mTestImsServiceCompat != null) {
+                return mTestImsServiceCompat;
+            }
+            mTestImsServiceCompat = new ImsServiceUT_compat(this);
+            return mTestImsServiceCompat;
+        }
+    }
+
     @Override
     public IBinder onBind(Intent intent) {
-        if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
-            if (ImsUtils.VDBG) {
-                Log.d(TAG, "onBind-Remote");
+        synchronized (mLock) {
+            if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+                mIsTelephonyBound = true;
+                if (mIsImsServiceCompat) {
+                    if (ImsUtils.VDBG) {
+                        Log.d(TAG, "onBind-Remote-Compat");
+                    }
+                    return getImsServiceCompat().onBind(intent);
+                } else {
+                    if (ImsUtils.VDBG) {
+                        Log.d(TAG, "onBind-Remote");
+                    }
+                    return getImsService().onBind(intent);
+                }
             }
-            return getImsService().onBind(intent);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "onBind-Local");
+            }
+            return mBinder;
         }
-        if (ImsUtils.VDBG) {
-            Log.i(TAG, "onBind-Local");
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        synchronized (mLock) {
+            if ("android.telephony.ims.ImsService".equals(intent.getAction())) {
+                if (ImsUtils.VDBG)  Log.i(TAG, "onUnbind-Remote");
+                mIsTelephonyBound = false;
+                countDownLatch(LATCH_ON_UNBIND);
+            } else {
+                if (ImsUtils.VDBG)  Log.i(TAG, "onUnbind-Local");
+            }
+            // return false so that onBind is called next time.
+            return false;
         }
-        return mBinder;
     }
 
     public void resetState() {
@@ -277,10 +579,38 @@
             mIsEnabled = false;
             mSetNullRcsBinding = false;
             mIsSipTransportImplemented = false;
+            mIsTestTypeExecutor = false;
+            mIsImsServiceCompat = false;
             mCapabilities = 0;
             for (int i = 0; i < LATCH_MAX; i++) {
                 sLatches[i] = new CountDownLatch(1);
             }
+
+            if (sMessageExecutor != null) {
+                sMessageExecutor.getLooper().quit();
+                sMessageExecutor = null;
+            }
+            mSubIDs.clear();
+        }
+    }
+
+    public boolean isTelephonyBound() {
+        return mIsTelephonyBound;
+    }
+
+    public void setExecutorTestType(boolean type) {
+        mIsTestTypeExecutor = type;
+        if (mIsTestTypeExecutor) {
+            if (sMessageExecutor == null) {
+                sMessageExecutor = new MessageExecutor("TestImsService");
+            }
+            mExecutor = sMessageExecutor;
+        }
+    }
+
+    public void setImsServiceCompat() {
+        synchronized (mLock) {
+            mIsImsServiceCompat = true;
         }
     }
 
@@ -329,20 +659,7 @@
     }
 
     public boolean waitForLatchCountdown(int latchIndex) {
-        boolean complete = false;
-        try {
-            CountDownLatch latch;
-            synchronized (mLock) {
-                latch = sLatches[latchIndex];
-            }
-            complete = latch.await(ImsUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            // complete == false
-        }
-        synchronized (mLock) {
-            sLatches[latchIndex] = new CountDownLatch(1);
-        }
-        return complete;
+        return waitForLatchCountdown(latchIndex, ImsUtils.TEST_TIMEOUT_MS);
     }
 
     public boolean waitForLatchCountdown(int latchIndex, long waitMs) {
@@ -352,7 +669,12 @@
             synchronized (mLock) {
                 latch = sLatches[latchIndex];
             }
+            long startTime = System.currentTimeMillis();
             complete = latch.await(waitMs, TimeUnit.MILLISECONDS);
+            if (ImsUtils.VDBG) {
+                Log.i(TAG, "Latch " + latchIndex + " took "
+                        + (System.currentTimeMillis() - startTime) + " ms to count down.");
+            }
         } catch (InterruptedException e) {
             // complete == false
         }
@@ -388,11 +710,15 @@
 
     public TestImsRegistration getImsRegistration() {
         synchronized (mLock) {
-            return sImsRegistrationImplBase;
+            return mImsRegistrationImplBase;
         }
     }
 
     public ImsConfigImplBase getConfig() {
         return mTestImsConfig;
     }
+
+    public HashSet<Integer> getSubIDs() {
+        return mSubIDs;
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
index cb82d71..d300b71 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestMmTelFeature.java
@@ -16,15 +16,20 @@
 
 package android.telephony.ims.cts;
 
+import android.os.Bundle;
+import android.telephony.ims.ImsCallProfile;
+import android.telephony.ims.ImsStreamMediaProfile;
 import android.telephony.ims.RtpHeaderExtensionType;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.stub.ImsCallSessionImplBase;
 import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 
 public class TestMmTelFeature extends MmTelFeature {
 
@@ -39,10 +44,26 @@
     private TestImsSmsImpl mSmsImpl;
     private Set<RtpHeaderExtensionType> mOfferedRtpHeaderExtensionTypes;
     private CountDownLatch mOfferedRtpHeaderExtensionLatch = new CountDownLatch(1);
+    private TestImsCallSessionImpl mCallSession;
 
     TestMmTelFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener removedListener,
             TestImsService.CapabilitiesSetListener setListener) {
+        Log.d(TAG, "TestMmTelFeature with default constructor");
+        mReadyListener = readyListener;
+        mRemovedListener = removedListener;
+        mCapSetListener = setListener;
+        mSmsImpl = new TestImsSmsImpl();
+        // Must set the state to READY in the constructor - onFeatureReady depends on the state
+        // being ready.
+        setFeatureState(STATE_READY);
+    }
+
+    TestMmTelFeature(TestImsService.ReadyListener readyListener,
+            TestImsService.RemovedListener removedListener,
+            TestImsService.CapabilitiesSetListener setListener, Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestMmTelFeature with Executor constructor");
         mReadyListener = readyListener;
         mRemovedListener = removedListener;
         mCapSetListener = setListener;
@@ -104,6 +125,26 @@
         mOfferedRtpHeaderExtensionLatch.countDown();
     }
 
+    @Override
+    public ImsCallProfile createCallProfile(int serviceType, int callType) {
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+        ImsCallProfile profile = new ImsCallProfile(serviceType, callType,
+                    new Bundle(), mediaProfile);
+        return profile;
+    }
+
+    @Override
+    public ImsCallSessionImplBase createCallSession(ImsCallProfile profile) {
+        ImsCallSessionImplBase s = new TestImsCallSessionImpl(profile);
+        mCallSession = (TestImsCallSessionImpl) s;
+        return s != null ? s : null;
+    }
+
     public void setCapabilities(MmTelCapabilities capabilities) {
         mCapabilities = capabilities;
     }
@@ -119,4 +160,34 @@
     public CountDownLatch getOfferedRtpHeaderExtensionLatch() {
         return mOfferedRtpHeaderExtensionLatch;
     }
+
+    public TestImsCallSessionImpl getImsCallsession() {
+        return mCallSession;
+    }
+
+    public boolean isCallSessionCreated() {
+        return (mCallSession != null);
+    }
+
+    public void onIncomingCallReceived(Bundle extras) {
+        Log.d(TAG, "onIncomingCallReceived");
+
+        ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile(
+                ImsStreamMediaProfile.AUDIO_QUALITY_AMR,
+                ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE,
+                ImsStreamMediaProfile.VIDEO_QUALITY_NONE,
+                ImsStreamMediaProfile.DIRECTION_INVALID,
+                ImsStreamMediaProfile.RTT_MODE_DISABLED);
+
+        ImsCallProfile callProfile = new ImsCallProfile(ImsCallProfile.SERVICE_TYPE_NORMAL,
+                ImsCallProfile.CALL_TYPE_VOICE, new Bundle(), mediaProfile);
+
+        TestImsCallSessionImpl incomingSession = new TestImsCallSessionImpl(callProfile);
+        mCallSession = incomingSession;
+
+        Executor executor = incomingSession.getExecutor();
+        executor.execute(() -> {
+            notifyIncomingCall(incomingSession, extras);
+        });
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 08eaa85..3915fc5 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -16,7 +16,6 @@
 
 package android.telephony.ims.cts;
 
-import android.content.Context;
 import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.RcsFeature;
 import android.telephony.ims.stub.CapabilityExchangeEventListener;
@@ -43,13 +42,30 @@
     private CapabilityExchangeEventListener mCapEventListener;
     private TestImsService.DeviceCapPublishListener mDeviceCapPublishListener;
 
-    TestRcsFeature(Context context,
-            TestImsService.ReadyListener readyListener,
+    TestRcsFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener removedListener,
             TestImsService.CapabilitiesSetListener setListener,
             TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener) {
-        super(context.getMainExecutor());
+        super();
+        Log.d(TAG, "TestRcsFeature with default constructor");
+        mReadyListener = readyListener;
+        mRemovedListener = removedListener;
+        mCapExchangeEventListener = capExchangeEventListener;
 
+        mRcsCapabilityChangedListener = setListener;
+        mRcsCapabilitiesLte = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+        mRcsCapabilitiesIWan = new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
+
+        setFeatureState(STATE_READY);
+    }
+
+    TestRcsFeature(TestImsService.ReadyListener readyListener,
+            TestImsService.RemovedListener removedListener,
+            TestImsService.CapabilitiesSetListener setListener,
+            TestImsService.RcsCapabilityExchangeEventListener capExchangeEventListener,
+            Executor executor) {
+        super(executor);
+        Log.d(TAG, "TestRcsFeature with Executor constructor");
         mReadyListener = readyListener;
         mRemovedListener = removedListener;
         mCapExchangeEventListener = capExchangeEventListener;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
index 0ea195c..88dcc2a 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestSipTransport.java
@@ -23,6 +23,7 @@
 import android.telephony.ims.DelegateStateCallback;
 import android.telephony.ims.stub.SipDelegate;
 import android.telephony.ims.stub.SipTransportImplBase;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 
@@ -34,6 +35,7 @@
 
 public class TestSipTransport extends SipTransportImplBase {
 
+    private static final String TAG = "TestSipTransport";
     public static final String ONE_TO_ONE_CHAT_TAG =
             "+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gppservice.ims.icsi.oma.cpm.msg\"";
     public static final String GROUP_CHAT_TAG =
@@ -54,8 +56,13 @@
     private final ArrayList<TestSipDelegate> mDelegates = new ArrayList<>();
     private final Object mLock = new Object();
 
+    public TestSipTransport() {
+        Log.d(TAG, "TestSipTransport with default constructor");
+    }
+
     public TestSipTransport(Executor executor) {
         super(executor);
+        Log.d(TAG, "TestSipTransport with Executor constructor");
     }
 
     @Override
diff --git a/tests/tests/telephony/sdk28/Android.bp b/tests/tests/telephony/sdk28/Android.bp
deleted file mode 100644
index bf23f39..0000000
--- a/tests/tests/telephony/sdk28/Android.bp
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-android_test {
-    name: "CtsTelephonySdk28TestCases",
-    defaults: ["cts_defaults"],
-    static_libs: [
-        "ctstestrunner-axt",
-        "compatibility-device-util-axt",
-    ],
-    srcs: ["src/**/*.java"],
-    // TODO: what can be done here? it used to be
-    // sdk_version: "28", but this is not compatible with compatibility-device-util-axt
-    sdk_version: "test_current",
-    // Tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    libs: [
-        "android.test.runner",
-        "android.test.base",
-    ],
-}
diff --git a/tests/tests/telephony/sdk28/AndroidManifest.xml b/tests/tests/telephony/sdk28/AndroidManifest.xml
deleted file mode 100644
index 7955df5..0000000
--- a/tests/tests/telephony/sdk28/AndroidManifest.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="android.telephony.sdk28.cts">
-    <uses-sdk android:targetSdkVersion="28"
-              android:minSdkVersion="28"
-              android:maxSdkVersion="28" />
-
-    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
-
-    <application>
-        <uses-library android:name="android.test.runner" />
-    </application>
-
-    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="android.telephony.sdk28.cts">
-        <meta-data android:name="listener"
-            android:value="com.android.cts.runner.CtsTestRunListener" />
-    </instrumentation>
-
-</manifest>
-
diff --git a/tests/tests/telephony/sdk28/AndroidTest.xml b/tests/tests/telephony/sdk28/AndroidTest.xml
deleted file mode 100644
index b165ade..0000000
--- a/tests/tests/telephony/sdk28/AndroidTest.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2015 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Config for CTS Telephony test cases">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="telecom" />
-    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
-    <option name="not-shardable" value="true" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
-    <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
-    <target_preparer class="android.telephony.cts.preconditions.TelephonyPreparer">
-        <option name="apk" value="CtsTelephonyPreparerApp.apk" />
-        <option name="package" value="android.telephony.cts.preconditions.preparerApp" />
-    </target_preparer>
-    <target_preparer class="android.telephony.cts.preconditions.TelephonyCleaner">
-        <option name="apk" value="CtsTelephonyCleanerApp.apk" />
-        <option name="package" value="android.telephony.cts.preconditions.cleanerApp" />
-    </target_preparer>
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="CtsTelephonySdk28TestCases.apk" />
-    </target_preparer>
-    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
-        <option name="package" value="android.telephony.sdk28.cts" />
-        <option name="hidden-api-checks" value="false"/>
-    </test>
-</configuration>
diff --git a/tests/tests/telephony/sdk28/TEST_MAPPING b/tests/tests/telephony/sdk28/TEST_MAPPING
deleted file mode 100644
index 141ee9e..0000000
--- a/tests/tests/telephony/sdk28/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
-  "presubmit": [
-    {
-      "name": "CtsTelephonySdk28TestCases"
-    }
-  ]
-}
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
deleted file mode 100644
index 09c890a..0000000
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/CellInfoTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.sdk28.cts;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.telephony.AccessNetworkConstants;
-import android.telephony.CellInfo;
-import android.telephony.NetworkRegistrationInfo;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import com.android.compatibility.common.util.ShellIdentityUtils;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-
-public class CellInfoTest {
-    private static final String TAG = "CellInfoTest";
-
-    private static final int MAX_WAIT_SECONDS = 15;
-    private static final int POLL_INTERVAL_MILLIS = 1000;
-
-    private static final String[] sPermissions = new String[] {
-            android.Manifest.permission.READ_PHONE_STATE,
-            android.Manifest.permission.ACCESS_COARSE_LOCATION};
-
-    private TelephonyManager mTm;
-    private PackageManager mPm;
-
-    private boolean isCamped() {
-        ServiceState ss = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTm, TelephonyManager::getServiceState);
-
-        if (ss == null) return false;
-        if (ss.getState() == ServiceState.STATE_EMERGENCY_ONLY) return true;
-        List<NetworkRegistrationInfo> nris = ss.getNetworkRegistrationInfoList();
-        for (NetworkRegistrationInfo nri : nris) {
-            if (nri.getTransportType() != AccessNetworkConstants.TRANSPORT_TYPE_WWAN) continue;
-            if (nri.isRegistered()) return true;
-        }
-        return false;
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        mTm = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
-        mPm = getContext().getPackageManager();
-
-        for (String permission : sPermissions) {
-            assertTrue("Something (not this test) has denied needed permission=" + permission,
-                    getContext().checkSelfPermission(permission)
-                            == android.content.pm.PackageManager.PERMISSION_GRANTED);
-        }
-    }
-
-    @Test
-    public void testCellInfoSdk28() {
-        if (!mPm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
-            return;
-        }
-
-        if (!isCamped()) fail("Device is not camped to a cell");
-
-        List<CellInfo> cellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                mTm, TelephonyManager::getAllCellInfo);
-
-        // getAllCellInfo should never return null, and there should be at least one entry.
-        assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", cellInfo);
-        assertFalse("TelephonyManager.getAllCellInfo() returned an empty list", cellInfo.isEmpty());
-
-        final long initialTime = cellInfo.get(0).getTimeStamp();
-
-        for(int i = 0; i < MAX_WAIT_SECONDS; i++) {
-            try {
-                Thread.sleep(POLL_INTERVAL_MILLIS); // 1 second
-            } catch (InterruptedException ie) {
-                fail("Thread was interrupted");
-            }
-            List<CellInfo> newCellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
-                    mTm, TelephonyManager::getAllCellInfo);
-            assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", newCellInfo);
-            assertFalse("TelephonyManager.getAllCellInfo() returned an empty list",
-                    newCellInfo.isEmpty());
-            // Test that new CellInfo has been retrieved from the modem
-            if (newCellInfo.get(0).getTimeStamp() != initialTime) return;
-        }
-        fail("CellInfo failed to update after " + MAX_WAIT_SECONDS + " seconds.");
-    }
-}
diff --git a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java b/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
deleted file mode 100644
index c890a2a..0000000
--- a/tests/tests/telephony/sdk28/src/android/telephony/sdk28/cts/PhoneStateListenerTest.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (C) 2009 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.telephony.sdk28.cts;
-
-import static androidx.test.InstrumentationRegistry.getContext;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Looper;
-import android.telephony.PhoneStateListener;
-import android.telephony.ServiceState;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-
-import androidx.test.filters.FlakyTest;
-
-import com.android.compatibility.common.util.TestThread;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-@FlakyTest
-public class PhoneStateListenerTest {
-
-    public static final long WAIT_TIME = 1000;
-    private boolean mOnServiceStateChangedCalled;
-    private TelephonyManager mTelephonyManager;
-    private PhoneStateListener mListener;
-    private final Object mLock = new Object();
-    private boolean mHasTelephony = true;
-    private static final String TAG = "android.telephony.cts.PhoneStateListenerTest";
-
-    @Before
-    public void setUp() throws Exception {
-        mTelephonyManager =
-                (TelephonyManager)getContext().getSystemService(Context.TELEPHONY_SERVICE);
-
-        PackageManager packageManager = getContext().getPackageManager();
-        if (packageManager != null) {
-            if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-                Log.d(TAG, "Some tests requiring FEATURE_TELEPHONY will be skipped");
-                mHasTelephony = false;
-            }
-        }
-    }
-
-    @After
-    public void tearDown() throws Exception {
-        if (mListener != null) {
-            // unregister the listener
-            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
-        }
-    }
-
-    @Test
-    public void testPhoneStateListener() {
-        Looper.prepare();
-        new PhoneStateListener();
-    }
-
-    @Test
-    public void testOnUnRegisterFollowedByRegister() throws Throwable {
-        if (!mHasTelephony) {
-            return;
-        }
-
-        TestThread t = new TestThread(new Runnable() {
-            public void run() {
-                Looper.prepare();
-
-                mListener = new PhoneStateListener() {
-                    @Override
-                    public void onServiceStateChanged(ServiceState serviceState) {
-                        synchronized(mLock) {
-                            mOnServiceStateChangedCalled = true;
-                            mLock.notify();
-                        }
-                    }
-                };
-                mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-
-                Looper.loop();
-            }
-        });
-
-        assertFalse(mOnServiceStateChangedCalled);
-        t.start();
-
-        synchronized (mLock) {
-            if (!mOnServiceStateChangedCalled){
-                mLock.wait(WAIT_TIME);
-            }
-        }
-        t.checkException();
-        assertTrue(mOnServiceStateChangedCalled);
-
-        // reset and un-register
-        mOnServiceStateChangedCalled = false;
-        if (mListener != null) {
-            // un-register the listener
-            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
-        }
-        synchronized (mLock) {
-            if (!mOnServiceStateChangedCalled){
-                mLock.wait(WAIT_TIME);
-            }
-        }
-        assertFalse(mOnServiceStateChangedCalled);
-
-        // re-register the listener
-        mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
-        synchronized (mLock) {
-            if (!mOnServiceStateChangedCalled){
-                mLock.wait(WAIT_TIME);
-            }
-        }
-        t.checkException();
-        assertTrue(mOnServiceStateChangedCalled);
-    }
-}
diff --git a/tests/tests/telephony2/README.md b/tests/tests/telephony2/README.md
new file mode 100644
index 0000000..38deeca
--- /dev/null
+++ b/tests/tests/telephony2/README.md
@@ -0,0 +1,5 @@
+# Telephony2 CTS tests
+
+This directory contains the set of Telephony CTS tests used to exercise permissions.
+For instance, we may want to verify that an API call throws an exception without
+READ_PHONE_STATE.
diff --git a/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java b/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
index b580930..25cd10c 100644
--- a/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
+++ b/tests/tests/telephony2/src/android/telephony2/cts/CallStateListenerPermissionTest.java
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.telephony.PhoneStateListener;
@@ -63,6 +64,7 @@
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getContext();
+        assumeTrue(mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY));
     }
 
     /**
@@ -71,10 +73,6 @@
      */
     @Test
     public void testRegisterWithNoCallLogPermission() {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
         assertNotNull(telephonyManager);
 
@@ -97,10 +95,6 @@
      */
     @Test
     public void testCallStatePermission() throws Exception {
-        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_TELEPHONY)) {
-            return;
-        }
-
         TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
         assertNotNull(telephonyManager);
         MyTelephonyCallback callback = new MyTelephonyCallback();
diff --git a/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java b/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java
new file mode 100644
index 0000000..214a3f7
--- /dev/null
+++ b/tests/tests/telephony2/src/android/telephony2/cts/TelephonyManagerReadPhoneStatePermissionTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony2.cts;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.platform.test.annotations.AppModeFull;
+import android.telecom.PhoneAccount;
+import android.telecom.TelecomManager;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.telephony.cts.TelephonyUtils;
+import android.telephony.emergency.EmergencyNumber;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test TelephonyManager APIs with READ_PHONE_STATE Permission.
+ */
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "Cannot grant the runtime permission in instant app mode")
+public class TelephonyManagerReadPhoneStatePermissionTest {
+
+    private boolean mHasTelephony;
+    TelephonyManager mTelephonyManager = null;
+    TelecomManager mTelecomManager = null;
+
+    @Before
+    public void setUp() throws Exception {
+        mHasTelephony = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_TELEPHONY);
+        mTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        assertNotNull(mTelephonyManager);
+        mTelecomManager =
+                (TelecomManager) getContext().getSystemService(Context.TELECOM_SERVICE);
+        assertNotNull(mTelecomManager);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        TelephonyUtils.resetCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                TelephonyUtils.CTS_APP_PACKAGE,
+                TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
+    }
+
+    /**
+     * Verify that TelephonyManager APIs requiring READ_PHONE_STATE Permission must work.
+     * <p>
+     * Requires Permission:
+     * {@link android.Manifest.permission#READ_PHONE_STATE}.
+     *
+     * APIs list:
+     * getDeviceSoftwareVersion()
+     * getCarrierConfig()
+     * getNetworkType()
+     * getDataNetworkType()
+     * getVoiceNetworkType()
+     * getGroupIdLevel1()
+     * getLine1AlphaTag()
+     * getVoiceMailNumber()
+     * getVisualVoicemailPackageName()
+     * getVoiceMailAlphaTag()
+     * getForbiddenPlmns()
+     * isDataRoamingEnabled()
+     * getSubscriptionId(@NonNull PhoneAccountHandle phoneAccountHandle)
+     * getServiceState()
+     * getEmergencyNumberList()
+     * getEmergencyNumberList(@EmergencyServiceCategories int categories)
+     * getPreferredOpportunisticDataSubscription()
+     * isModemEnabledForSlot(int slotIndex)
+     * isMultiSimSupported()
+     * doesSwitchMultiSimConfigTriggerReboot()
+     * getCallState() (when compat fwk enables enforcement)
+     * getCallStateForSubscription() (when compat fwk enables enforcement)
+     */
+    @Test
+    public void testTelephonyManagersAPIsRequiringReadPhoneStatePermissions() throws Exception {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        try {
+            // We must ensure that compat fwk enables READ_PHONE_STATE enforcement
+            TelephonyUtils.enableCompatCommand(InstrumentationRegistry.getInstrumentation(),
+                    TelephonyUtils.CTS_APP_PACKAGE,
+                    TelephonyUtils.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION_STRING);
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getCallState());
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getCallStateForSubscription());
+        } catch (SecurityException e) {
+            fail("TelephonyManager#getCallState and TelephonyManager#getCallStateForSubscription "
+                    + "must not throw a SecurityException because READ_PHONE_STATE permission is "
+                    + "granted and TelecomManager#ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION is "
+                    + "enabled.");
+        }
+
+        int subId = mTelephonyManager.getSubscriptionId();
+
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getNetworkType());
+        } catch (SecurityException e) {
+            fail("getNetworkType() must not throw a SecurityException with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getDeviceSoftwareVersion());
+        } catch (SecurityException e) {
+            fail("getDeviceSoftwareVersion() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getCarrierConfig());
+        } catch (SecurityException e) {
+            fail("getCarrierConfig() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getDataNetworkType());
+        } catch (SecurityException e) {
+            fail("getDataNetworkType() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getVoiceNetworkType());
+        } catch (SecurityException e) {
+            fail("getVoiceNetworkType() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getGroupIdLevel1());
+        } catch (SecurityException e) {
+            fail("getGroupIdLevel1() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getLine1AlphaTag());
+        } catch (SecurityException e) {
+            fail("getLine1AlphaTag() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getVoiceMailNumber());
+        } catch (SecurityException e) {
+            fail("getVoiceMailNumber() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getVisualVoicemailPackageName());
+        } catch (SecurityException e) {
+            fail("getVisualVoicemailPackageName() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getVoiceMailAlphaTag());
+        } catch (SecurityException e) {
+            fail("getVoiceMailAlphaTag() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getForbiddenPlmns());
+        } catch (SecurityException e) {
+            fail("getForbiddenPlmns() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.isDataRoamingEnabled());
+        } catch (SecurityException e) {
+            fail("isDataRoamingEnabled() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getSubscriptionId(
+                            mTelecomManager.getDefaultOutgoingPhoneAccount(
+                                    PhoneAccount.SCHEME_TEL)));
+        } catch (SecurityException e) {
+            fail("getSubscriptionId(phoneAccountHandle) must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getServiceState());
+        } catch (SecurityException e) {
+            fail("getServiceState() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getEmergencyNumberList());
+        } catch (SecurityException e) {
+            fail("getEmergencyNumberList() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getEmergencyNumberList(
+                            EmergencyNumber.EMERGENCY_SERVICE_CATEGORY_POLICE));
+        } catch (SecurityException e) {
+            fail("getEmergencyNumberList(EMERGENCY_SERVICE_CATEGORY_POLICE) must"
+                    + " not throw a SecurityException with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.getPreferredOpportunisticDataSubscription());
+        } catch (SecurityException e) {
+            fail("getPreferredOpportunisticDataSubscription() must not throw"
+                    + " a SecurityException with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.isModemEnabledForSlot(
+                            SubscriptionManager.getSlotIndex(subId)));
+        } catch (SecurityException e) {
+            fail("isModemEnabledForSlot(slotIndex) must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.isMultiSimSupported());
+        } catch (SecurityException e) {
+            fail("isMultiSimSupported() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTelephonyManager, (tm) -> tm.doesSwitchMultiSimConfigTriggerReboot());
+        } catch (SecurityException e) {
+            fail("doesSwitchMultiSimConfigTriggerReboot() must not throw a SecurityException"
+                    + " with READ_PHONE_STATE" + e);
+        }
+    }
+
+    private static Context getContext() {
+        return InstrumentationRegistry.getContext();
+    }
+}
diff --git a/tests/tests/telephony3/Android.bp b/tests/tests/telephony3/Android.bp
index 5284cda..e5697e5 100644
--- a/tests/tests/telephony3/Android.bp
+++ b/tests/tests/telephony3/Android.bp
@@ -19,7 +19,10 @@
 android_test {
     name: "CtsTelephony3TestCases",
     defaults: ["cts_defaults"],
-    static_libs: ["ctstestrunner-axt"],
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+    ],
     srcs: ["src/**/*.java"],
     // The SDK version is set to 28 to test device identifier access for apps with
     // the READ_PHONE_STATE permission targeting pre-Q.
diff --git a/tests/tests/telephony3/AndroidManifest.xml b/tests/tests/telephony3/AndroidManifest.xml
index 3a53a6c..22372b8 100644
--- a/tests/tests/telephony3/AndroidManifest.xml
+++ b/tests/tests/telephony3/AndroidManifest.xml
@@ -18,6 +18,7 @@
     package="android.telephony3.cts">
 
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/telephony3/README.md b/tests/tests/telephony3/README.md
new file mode 100644
index 0000000..b99b747
--- /dev/null
+++ b/tests/tests/telephony3/README.md
@@ -0,0 +1,4 @@
+# Telephony3 CTS tests
+
+This directory contains the set of Telephony CTS tests which verify behavior on
+sdk28 (Android P, which was released in 2018).
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java b/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java
new file mode 100644
index 0000000..9eb6aa0
--- /dev/null
+++ b/tests/tests/telephony3/src/android/telephony3/cts/CellInfoTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony3.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.telephony.CellInfo;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+public class CellInfoTest {
+    private static final String TAG = "CellInfoTest";
+
+    private static final int MAX_WAIT_SECONDS = 15;
+    private static final int POLL_INTERVAL_MILLIS = 1000;
+
+    private static final String[] sPermissions = new String[] {
+            android.Manifest.permission.READ_PHONE_STATE,
+            android.Manifest.permission.ACCESS_COARSE_LOCATION};
+
+    private TelephonyManager mTm;
+    private PackageManager mPm;
+
+    private boolean isCamped() {
+        return (mTm.getVoiceNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN
+                || mTm.getDataNetworkType() != TelephonyManager.NETWORK_TYPE_UNKNOWN);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mTm = (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+        mPm = getContext().getPackageManager();
+
+        for (String permission : sPermissions) {
+            assertTrue("Something (not this test) has denied needed permission=" + permission,
+                    getContext().checkSelfPermission(permission)
+                            == android.content.pm.PackageManager.PERMISSION_GRANTED);
+        }
+    }
+
+    @Test
+    public void testCellInfoSdk28() {
+        if (!mPm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+            return;
+        }
+
+        if (!isCamped()) fail("Device is not camped to a cell");
+
+        List<CellInfo> cellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTm, TelephonyManager::getAllCellInfo);
+
+        // getAllCellInfo should never return null, and there should be at least one entry.
+        assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", cellInfo);
+        assertFalse("TelephonyManager.getAllCellInfo() returned an empty list", cellInfo.isEmpty());
+
+        final long initialTime = cellInfo.get(0).getTimeStamp();
+
+        for (int i = 0; i < MAX_WAIT_SECONDS; i++) {
+            try {
+                Thread.sleep(POLL_INTERVAL_MILLIS); // 1 second
+            } catch (InterruptedException ie) {
+                fail("Thread was interrupted");
+            }
+            List<CellInfo> newCellInfo = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                    mTm, TelephonyManager::getAllCellInfo);
+            assertNotNull("TelephonyManager.getAllCellInfo() returned NULL CellInfo", newCellInfo);
+            assertFalse("TelephonyManager.getAllCellInfo() returned an empty list",
+                    newCellInfo.isEmpty());
+            // Test that new CellInfo has been retrieved from the modem
+            if (newCellInfo.get(0).getTimeStamp() != initialTime) return;
+        }
+        fail("CellInfo failed to update after " + MAX_WAIT_SECONDS + " seconds.");
+    }
+}
diff --git a/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java b/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java
new file mode 100644
index 0000000..01b3a2a
--- /dev/null
+++ b/tests/tests/telephony3/src/android/telephony3/cts/PhoneStateListenerTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.telephony3.cts;
+
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Looper;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import androidx.test.filters.FlakyTest;
+
+import com.android.compatibility.common.util.TestThread;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+@FlakyTest
+public class PhoneStateListenerTest {
+
+    public static final long WAIT_TIME = 1000;
+    private boolean mOnServiceStateChangedCalled;
+    private TelephonyManager mTelephonyManager;
+    private PhoneStateListener mListener;
+    private final Object mLock = new Object();
+    private boolean mHasTelephony = true;
+    private static final String TAG = "android.telephony.cts.PhoneStateListenerTest";
+
+    @Before
+    public void setUp() throws Exception {
+        mTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+
+        PackageManager packageManager = getContext().getPackageManager();
+        if (packageManager != null) {
+            if (!packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+                Log.d(TAG, "Some tests requiring FEATURE_TELEPHONY will be skipped");
+                mHasTelephony = false;
+            }
+        }
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (mListener != null) {
+            // unregister the listener
+            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
+        }
+    }
+
+    @Test
+    public void testPhoneStateListener() {
+        Looper.prepare();
+        new PhoneStateListener();
+    }
+
+    @Test
+    public void testOnUnRegisterFollowedByRegister() throws Throwable {
+        if (!mHasTelephony) {
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onServiceStateChanged(ServiceState serviceState) {
+                        synchronized (mLock) {
+                            mOnServiceStateChangedCalled = true;
+                            mLock.notify();
+                        }
+                    }
+                };
+                mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+
+                Looper.loop();
+            }
+        });
+
+        assertFalse(mOnServiceStateChangedCalled);
+        t.start();
+
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertTrue(mOnServiceStateChangedCalled);
+
+        // reset and un-register
+        mOnServiceStateChangedCalled = false;
+        if (mListener != null) {
+            // un-register the listener
+            mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_NONE);
+        }
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        assertFalse(mOnServiceStateChangedCalled);
+
+        // re-register the listener
+        mTelephonyManager.listen(mListener, PhoneStateListener.LISTEN_SERVICE_STATE);
+        synchronized (mLock) {
+            if (!mOnServiceStateChangedCalled) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertTrue(mOnServiceStateChangedCalled);
+    }
+}
diff --git a/tests/tests/telephony4/README.md b/tests/tests/telephony4/README.md
new file mode 100644
index 0000000..2a41615
--- /dev/null
+++ b/tests/tests/telephony4/README.md
@@ -0,0 +1,4 @@
+# Telephony4 CTS tests
+
+This directory contains the set of Telephony CTS tests which involve carrier privileges.
+The test apk is signed with android_telephony_cts_testkey.
diff --git a/tests/tests/text/OWNERS b/tests/tests/text/OWNERS
index 73d670f6..b52adb9 100644
--- a/tests/tests/text/OWNERS
+++ b/tests/tests/text/OWNERS
@@ -4,6 +4,5 @@
 siyamed@google.com
 nona@google.com
 clarabayarri@google.com
-nfuller@google.com
 ngeoffray@google.com
 vichang@google.com
diff --git a/tests/tests/text/assets/fonts/LowGlyphFont.ttf b/tests/tests/text/assets/fonts/LowGlyphFont.ttf
new file mode 100644
index 0000000..751926e
--- /dev/null
+++ b/tests/tests/text/assets/fonts/LowGlyphFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/LowGlyphFont.ttx b/tests/tests/text/assets/fonts/LowGlyphFont.ttx
new file mode 100644
index 0000000..f613ee8
--- /dev/null
+++ b/tests/tests/text/assets/fonts/LowGlyphFont.ttx
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="a"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="1000"/>
+    <descent value="-1000"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="a" width="1000" lsb="93"/>  <!-- 3em -->
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x0061" name="a" />
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="a" xMin="0" yMin="-1000" xMax="1000" yMax="1000">
+      <contour>
+        <pt x="0" y="-1000" on="1" />
+        <pt x="0" y="1000" on="1" />
+        <pt x="1000" y="1000" on="1" />
+        <pt x="1000" y="-1000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      LowGlyph Test Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      LowGlyph Test Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttf b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttf
new file mode 100644
index 0000000..42a7362b
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx
new file mode 100644
index 0000000..c7ad6e6
--- /dev/null
+++ b/tests/tests/text/assets/fonts/StaticLayoutTouchLocation.ttx
@@ -0,0 +1,189 @@
+<?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.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="1em"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Thu Feb 15 18:29:10 2018"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="0x00010000"/>
+    <ascent value="1000"/>
+    <descent value="-200"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="100"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="1em" width="1000" lsb="0"/>
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+        <map code="0x0020" name="1em" /> <!-- SPACE -->
+        <map code="0x0061" name="1em" /> <!-- SPACE -->
+        <map code="0x0627" name="1em" /> <!-- SPACE -->
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="1em" xMin="0" yMin="0" xMax="1000" yMax="1000">
+        <contour>
+            <pt x="0" y="0" on="1"/>
+            <pt x="1000" y="500" on="1"/>
+            <pt x="0" y="1000" on="1"/>
+        </contour>
+        <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2021 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      Sample Font For Touch Location Test
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      Sample Font For Touch Location Test
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFontForTouchLocationTest-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/text/assets/fonts/TallGlyphFont.ttf b/tests/tests/text/assets/fonts/TallGlyphFont.ttf
new file mode 100644
index 0000000..b6768e3
--- /dev/null
+++ b/tests/tests/text/assets/fonts/TallGlyphFont.ttf
Binary files differ
diff --git a/tests/tests/text/assets/fonts/TallGlyphFont.ttx b/tests/tests/text/assets/fonts/TallGlyphFont.ttx
new file mode 100644
index 0000000..5855933
--- /dev/null
+++ b/tests/tests/text/assets/fonts/TallGlyphFont.ttx
@@ -0,0 +1,188 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<ttFont sfntVersion="\x00\x01\x00\x00" ttLibVersion="3.0">
+
+  <GlyphOrder>
+    <GlyphID id="0" name=".notdef"/>
+    <GlyphID id="1" name="U+1000"/>
+  </GlyphOrder>
+
+  <head>
+    <tableVersion value="1.0"/>
+    <fontRevision value="1.0"/>
+    <checkSumAdjustment value="0x640cdb2f"/>
+    <magicNumber value="0x5f0f3cf5"/>
+    <flags value="00000000 00000011"/>
+    <unitsPerEm value="1000"/>
+    <created value="Fri Mar 17 07:26:00 2017"/>
+    <macStyle value="00000000 00000000"/>
+    <lowestRecPPEM value="7"/>
+    <fontDirectionHint value="2"/>
+    <glyphDataFormat value="0"/>
+  </head>
+
+  <hhea>
+    <tableVersion value="1.0"/>
+    <ascent value="3000"/>
+    <descent value="-3000"/>
+    <lineGap value="0"/>
+    <caretSlopeRise value="1"/>
+    <caretSlopeRun value="0"/>
+    <caretOffset value="0"/>
+    <reserved0 value="0"/>
+    <reserved1 value="0"/>
+    <reserved2 value="0"/>
+    <reserved3 value="0"/>
+    <metricDataFormat value="0"/>
+  </hhea>
+
+  <maxp>
+    <tableVersion value="0x10000"/>
+    <maxZones value="0"/>
+    <maxTwilightPoints value="0"/>
+    <maxStorage value="0"/>
+    <maxFunctionDefs value="0"/>
+    <maxInstructionDefs value="0"/>
+    <maxStackElements value="0"/>
+    <maxSizeOfInstructions value="0"/>
+    <maxComponentElements value="0"/>
+  </maxp>
+
+  <OS_2>
+    <!-- The fields 'usFirstCharIndex' and 'usLastCharIndex'
+         will be recalculated by the compiler -->
+    <version value="3"/>
+    <xAvgCharWidth value="594"/>
+    <usWeightClass value="400"/>
+    <usWidthClass value="5"/>
+    <fsType value="00000000 00001000"/>
+    <ySubscriptXSize value="650"/>
+    <ySubscriptYSize value="600"/>
+    <ySubscriptXOffset value="0"/>
+    <ySubscriptYOffset value="75"/>
+    <ySuperscriptXSize value="650"/>
+    <ySuperscriptYSize value="600"/>
+    <ySuperscriptXOffset value="0"/>
+    <ySuperscriptYOffset value="350"/>
+    <yStrikeoutSize value="50"/>
+    <yStrikeoutPosition value="300"/>
+    <sFamilyClass value="0"/>
+    <panose>
+      <bFamilyType value="0"/>
+      <bSerifStyle value="0"/>
+      <bWeight value="5"/>
+      <bProportion value="0"/>
+      <bContrast value="0"/>
+      <bStrokeVariation value="0"/>
+      <bArmStyle value="0"/>
+      <bLetterForm value="0"/>
+      <bMidline value="0"/>
+      <bXHeight value="0"/>
+    </panose>
+    <ulUnicodeRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulUnicodeRange2 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange3 value="00000000 00000000 00000000 00000000"/>
+    <ulUnicodeRange4 value="00000000 00000000 00000000 00000000"/>
+    <achVendID value="UKWN"/>
+    <fsSelection value="00000000 01000000"/>
+    <usFirstCharIndex value="32"/>
+    <usLastCharIndex value="122"/>
+    <sTypoAscender value="800"/>
+    <sTypoDescender value="-200"/>
+    <sTypoLineGap value="200"/>
+    <usWinAscent value="1000"/>
+    <usWinDescent value="200"/>
+    <ulCodePageRange1 value="00000000 00000000 00000000 00000001"/>
+    <ulCodePageRange2 value="00000000 00000000 00000000 00000000"/>
+    <sxHeight value="500"/>
+    <sCapHeight value="700"/>
+    <usDefaultChar value="0"/>
+    <usBreakChar value="32"/>
+    <usMaxContext value="0"/>
+  </OS_2>
+
+  <hmtx>
+    <mtx name=".notdef" width="500" lsb="93"/>
+    <mtx name="U+1000" width="3000" lsb="93"/>  <!-- 3em -->
+  </hmtx>
+
+  <cmap>
+    <tableVersion version="0"/>
+    <cmap_format_4 platformID="3" platEncID="10" language="0">
+      <map code="0x1000" name="U+1000" />
+    </cmap_format_4>
+  </cmap>
+
+  <loca>
+    <!-- The 'loca' table will be calculated by the compiler -->
+  </loca>
+
+  <glyf>
+    <TTGlyph name=".notdef" xMin="0" yMin="0" xMax="0" yMax="0" />
+    <TTGlyph name="U+1000" xMin="0" yMin="-3000" xMax="3000" yMax="3000">
+      <contour>
+        <pt x="0" y="-3000" on="1" />
+        <pt x="0" y="3000" on="1" />
+        <pt x="3000" y="3000" on="1" />
+        <pt x="3000" y="-3000" on="1" />
+      </contour>
+      <instructions />
+    </TTGlyph>
+  </glyf>
+
+  <name>
+    <namerecord nameID="0" platformID="3" platEncID="1" langID="0x409">
+      Copyright (C) 2017 The Android Open Source Project
+    </namerecord>
+    <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409">
+      TallGlyph Test Font
+    </namerecord>
+    <namerecord nameID="2" platformID="3" platEncID="1" langID="0x409">
+      Regular
+    </namerecord>
+    <namerecord nameID="4" platformID="3" platEncID="1" langID="0x409">
+      TallGlyph Test Font
+    </namerecord>
+    <namerecord nameID="6" platformID="3" platEncID="1" langID="0x409">
+      SampleFont-Regular
+    </namerecord>
+    <namerecord nameID="13" platformID="3" platEncID="1" langID="0x409">
+      Licensed under the Apache License, Version 2.0 (the "License");
+      you may not use this file except in compliance with the License.
+      Unless required by applicable law or agreed to in writing, software
+      distributed under the License is distributed on an "AS IS" BASIS
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      See the License for the specific language governing permissions and
+      limitations under the License.
+    </namerecord>
+    <namerecord nameID="14" platformID="3" platEncID="1" langID="0x409">
+      http://www.apache.org/licenses/LICENSE-2.0
+    </namerecord>
+  </name>
+
+  <post>
+    <formatType value="3.0"/>
+    <italicAngle value="0.0"/>
+    <underlinePosition value="-75"/>
+    <underlineThickness value="50"/>
+    <isFixedPitch value="0"/>
+    <minMemType42 value="0"/>
+    <maxMemType42 value="0"/>
+    <minMemType1 value="0"/>
+    <maxMemType1 value="0"/>
+  </post>
+
+</ttFont>
diff --git a/tests/tests/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java
new file mode 100644
index 0000000..9c59b62
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/BoringLayoutFallbackLineSpacingTest.java
@@ -0,0 +1,160 @@
+/*
+ * 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.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.platform.test.annotations.Presubmit;
+import android.text.BoringLayout;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+import android.text.style.TypefaceSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class BoringLayoutFallbackLineSpacingTest {
+
+    @Test
+    public void testFallbackSpacing_Metrics() throws IOException {
+        AssetManager am =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+                ).build()
+        ).addCustomFallback(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+                ).build()
+        ).build();
+
+        TextPaint p = new TextPaint();
+        p.setTextSize(100);  // make 1 em = 100 pixels
+        p.setTypeface(typeface);
+
+        String text = "a\u1000";
+        BoringLayout.Metrics boring = BoringLayout.isBoring(
+                text, p, TextDirectionHeuristics.LTR, false /* useFallbackLineSpacing */, null);
+
+        BoringLayout.Metrics fallbackLineSpacingBoring = BoringLayout.isBoring(
+                text, p, TextDirectionHeuristics.LTR, true /* useFallbackLineSpacing */, null);
+
+
+        // LowGlyphFont has -1 em ascent and 1 em descent. TallGlyphFont has -3em ascent and 3em
+        // descent.
+        assertThat(boring.ascent).isEqualTo(-100);
+        assertThat(boring.descent).isEqualTo(100);
+        assertThat(fallbackLineSpacingBoring.ascent).isEqualTo(-300);
+        assertThat(fallbackLineSpacingBoring.descent).isEqualTo(300);
+    }
+
+    @Test
+    public void testFallbackSpacing_Layout() throws IOException {
+        AssetManager am =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+                ).build()
+        ).addCustomFallback(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+                ).build()
+        ).build();
+
+        TextPaint p = new TextPaint();
+        p.setTextSize(100);  // make 1 em = 100 pixels
+        p.setTypeface(typeface);
+
+        String text = "a\u1000";
+        BoringLayout.Metrics metrics = BoringLayout.isBoring(text, p, TextDirectionHeuristics.LTR,
+                false /* useFallbackLineSpacing */, null);
+        BoringLayout layout = BoringLayout.make(
+                text, p,
+                Integer.MAX_VALUE /* outer width */,
+                Layout.Alignment.ALIGN_NORMAL,
+                1.0f /* spacing mult */,
+                0.0f /* spacing add */,
+                metrics,
+                false /* includePad */);
+        BoringLayout includePadLayout = BoringLayout.make(
+                text, p,
+                Integer.MAX_VALUE /* outer width */,
+                Layout.Alignment.ALIGN_NORMAL,
+                1.0f /* spacing mult */,
+                0.0f /* spacing add */,
+                metrics,
+                true /* includePad */);
+
+        BoringLayout.Metrics fallbackMetrics = BoringLayout.isBoring(text, p,
+                TextDirectionHeuristics.LTR, true /* useFallbackLineSpacing */, null);
+        BoringLayout fallbackLayout = BoringLayout.make(
+                text, p,
+                Integer.MAX_VALUE /* outer width */,
+                Layout.Alignment.ALIGN_NORMAL,
+                1.0f /* spacing mult */,
+                0.0f /* spacing add */,
+                fallbackMetrics,
+                false /* includePad */);
+        BoringLayout includePadFallbackLayout = BoringLayout.make(
+                text, p,
+                Integer.MAX_VALUE /* outer width */,
+                Layout.Alignment.ALIGN_NORMAL,
+                1.0f /* spacing mult */,
+                0.0f /* spacing add */,
+                fallbackMetrics,
+                true /* includePad */);
+
+
+        // LowGlyphFont has -1 em ascent and 1 em descent. TallGlyphFont has -3em ascent and 3em
+        // descent.
+        assertThat(layout.getLineBottom(0)).isEqualTo(200);
+        assertThat(includePadLayout.getLineBottom(0)).isEqualTo(200);
+        assertThat(fallbackLayout.getLineBottom(0)).isEqualTo(600);
+        assertThat(includePadFallbackLayout.getLineBottom(0)).isEqualTo(600);
+    }
+
+    @Test
+    public void testReplacementSpans() {
+        SpannableString ss = new SpannableString("Hello, World.");
+        ss.setSpan(new TypefaceSpan(Typeface.SERIF), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+        TextPaint paint = new TextPaint();
+
+        BoringLayout.isBoring(ss, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, true, null);
+    }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java
new file mode 100644
index 0000000..2717cc4
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/FallbackLineSpacingTest.java
@@ -0,0 +1,233 @@
+/*
+ * 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.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.platform.test.annotations.Presubmit;
+import android.text.TextPaint;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FallbackLineSpacingTest {
+
+    @Test
+    public void testFallbackSpacing() throws IOException {
+        AssetManager am =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+                ).build()
+        ).addCustomFallback(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+                ).build()
+        ).build();
+
+        TextPaint p = new TextPaint();
+        p.setTextSize(100);  // make 1 em = 100 pixels
+        p.setTypeface(typeface);
+
+        String text = "a\u1000";
+
+        Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+        p.getFontMetricsInt(text, 0, text.length(), 0, text.length(), false, fmi);
+        assertThat(fmi.top).isEqualTo(-300);
+        assertThat(fmi.ascent).isEqualTo(-300);
+        assertThat(fmi.descent).isEqualTo(300);
+        assertThat(fmi.bottom).isEqualTo(300);
+
+        p.getFontMetricsInt(text, 0, 1, 0, text.length(), false, fmi);
+        assertThat(fmi.top).isEqualTo(-100);
+        assertThat(fmi.ascent).isEqualTo(-100);
+        assertThat(fmi.descent).isEqualTo(100);
+        assertThat(fmi.bottom).isEqualTo(100);
+
+        p.getFontMetricsInt(text, 1, 1, 0, text.length(), false, fmi);
+        assertThat(fmi.top).isEqualTo(-300);
+        assertThat(fmi.ascent).isEqualTo(-300);
+        assertThat(fmi.descent).isEqualTo(300);
+        assertThat(fmi.bottom).isEqualTo(300);
+    }
+
+    @Test
+    public void testEmptyString() {
+        Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100 pixels
+        paint.getFontMetricsInt("a", 0, 0, 0, 0, false, fmi);
+
+        Paint.FontMetricsInt noTextFmi = paint.getFontMetricsInt();
+
+        assertThat(fmi).isEqualTo(noTextFmi);
+    }
+
+    @Test
+    public void testEmptyCharArray() {
+        Paint.FontMetricsInt fmi = new Paint.FontMetricsInt();
+        Paint paint = new Paint();
+        paint.setTextSize(100);  // make 1em = 100 pixels
+        paint.getFontMetricsInt("a".toCharArray(), 0, 0, 0, 0, false, fmi);
+
+        Paint.FontMetricsInt noTextFmi = paint.getFontMetricsInt();
+
+        assertThat(fmi).isEqualTo(noTextFmi);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullTextArgument_String() {
+        new Paint().getFontMetricsInt((String) null, 0, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testNullTextArgument_CharArgument() {
+        new Paint().getFontMetricsInt((char[]) null, 0, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    // start argument test cases
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallStartArgument_String() {
+        new Paint().getFontMetricsInt("a", -1, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallStartArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), -1, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeStartArgument_String() {
+        new Paint().getFontMetricsInt("a", 2, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeStartArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 2, 0, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    // count argument test cases
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallCountArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, -1, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmalCountArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, -1, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCountArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 2, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCountArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 2, 0, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    // ctxStart argument test cases
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallCtxStartArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 1, -1, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallCtxStartArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, -1, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCtxStartArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 1, 2, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCtxStartArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 2, 0, false,
+                new Paint.FontMetricsInt());
+    }
+
+    // count argument test cases
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallCtxCountArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 1, 0, -1, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmalCtxCountArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, -1, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCtxCountArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 1, 0, 2, false,
+                new Paint.FontMetricsInt());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testLargeCtxCountArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, 2, false,
+                new Paint.FontMetricsInt());
+    }
+
+    // count argument test cases
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmallNullOutArgument_String() {
+        new Paint().getFontMetricsInt("a", 0, 1, 0, 1, false, null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testSmalNullOutArgument_CharArray() {
+        new Paint().getFontMetricsInt("a".toCharArray(), 0, 1, 0, 1, false, null);
+    }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/FontFileTestUtil.java b/tests/tests/text/src/android/text/cts/FontFileTestUtil.java
new file mode 100644
index 0000000..6fbab1f
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/FontFileTestUtil.java
@@ -0,0 +1,100 @@
+/*
+ * 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.text.cts;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+
+public class FontFileTestUtil {
+    private static final int SFNT_VERSION_1 = 0x00010000;
+    private static final int SFNT_VERSION_OTTO = 0x4F54544F;
+    private static final int TTC_TAG = 0x74746366;
+    private static final int NAME_TAG = 0x6E616D65;
+    private static final int GPOS_TAG = 0x47504F53;
+    private static final int CHWS_TAG = 0x63687773;
+
+    public static String getPostScriptName(File file, int index) {
+        try (FileInputStream fis = new FileInputStream(file)) {
+            final FileChannel fc = fis.getChannel();
+            long size = fc.size();
+            ByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, size)
+                    .order(ByteOrder.BIG_ENDIAN);
+
+            int magicNumber = buffer.getInt(0);
+
+            int fontOffset = 0;
+            int numFonts = buffer.getInt(8);
+            if (index >= numFonts) {
+                return null;
+            }
+
+            if (magicNumber == TTC_TAG) {
+                fontOffset = buffer.getInt(12 + 4 * index);
+                magicNumber = buffer.getInt(fontOffset);
+                if (magicNumber != SFNT_VERSION_1 && magicNumber != SFNT_VERSION_OTTO) {
+                    throw new IOException("Unknown magic number at 0th font: #" + magicNumber);
+                }
+            } else if (magicNumber != SFNT_VERSION_1 && magicNumber != SFNT_VERSION_OTTO) {
+                throw new IOException("Unknown magic number: #" + magicNumber);
+            }
+
+            int numTables = buffer.getShort(fontOffset + 4);  // offset to number of table
+            int nameTableOffset = 0;
+            for (int i = 0; i < numTables; ++i) {
+                int tableEntryOffset = fontOffset + 12 + i * 16;
+                int tableTag = buffer.getInt(tableEntryOffset);
+                if (tableTag == NAME_TAG) {
+                    nameTableOffset = buffer.getInt(tableEntryOffset + 8);
+                    break;
+                }
+            }
+
+            if (nameTableOffset == 0) {
+                throw new IOException("name table not found.");
+            }
+
+            int nameTableCount = buffer.getShort(nameTableOffset + 2);
+            int storageOffset = buffer.getShort(nameTableOffset + 4);
+
+            for (int i = 0; i < nameTableCount; ++i) {
+                int platformID = buffer.getShort(nameTableOffset + 6 + i * 12);
+                int encodingID = buffer.getShort(nameTableOffset + 6 + i * 12 + 2);
+                int languageID = buffer.getShort(nameTableOffset + 6 + i * 12 + 4);
+                int nameID = buffer.getShort(nameTableOffset + 6 + i * 12 + 6);
+                int length = buffer.getShort(nameTableOffset + 6 + i * 12 + 8);
+                int stringOffset = buffer.getShort(nameTableOffset + 6 + i * 12 + 10);
+
+                if (nameID == 6 && platformID == 3 && encodingID == 1 && languageID == 1033) {
+                    byte[] name = new byte[length];
+                    ByteBuffer slice = buffer.slice();
+                    slice.position(nameTableOffset + storageOffset + stringOffset);
+                    slice.get(name);
+                    // encoded in UTF-16BE for platform ID = 3
+                    return new String(name, StandardCharsets.UTF_16BE);
+                }
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+        return null;
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java b/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java
new file mode 100644
index 0000000..ef751ce
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/NotoCJKFontRequirement.java
@@ -0,0 +1,135 @@
+/*
+ * 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.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.Resources;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.fonts.SystemFonts;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.icu.util.ULocale;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class NotoCJKFontRequirement {
+
+    private static final String TEST_OPEN_DOUBLE_KEY_BRACKET = "\u300C\u300E";
+
+    private static final Set<String> CJK_SCRIPT = new HashSet<>(Arrays.asList(
+            "Hani",  // General Han character
+            "Hans",  // Simplified Chinese
+            "Hant",  // Tranditional Chinese
+            "Hira", "Hrkt", "Jpan", "Kana",  // Japanese
+            "Hang", "Kore" // Hangul
+    ));
+
+    private boolean isCJKSupported() {
+        final String[] localeNames = Resources.getSystem().getStringArray(
+                Resources.getSystem().getIdentifier("supported_locales", "array", "android"));
+        for (String locale : localeNames) {
+            final ULocale uLocale = ULocale.addLikelySubtags(ULocale.forLanguageTag(locale));
+            final String script = uLocale.getScript();
+
+            if (CJK_SCRIPT.contains(script)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    // List of CJK font configuration.
+    private static final Set<String> CJK_FONT_PSNAMES = new HashSet<>();
+
+    static {
+        CJK_FONT_PSNAMES.add("NotoSansCJKsc-Regular");
+        CJK_FONT_PSNAMES.add("NotoSansCJKtc-Regular");
+        CJK_FONT_PSNAMES.add("NotoSansCJKjp-Regular");
+        CJK_FONT_PSNAMES.add("NotoSansCJKkr-Regular");
+        CJK_FONT_PSNAMES.add("NotoSerifCJKsc-Regular");
+        CJK_FONT_PSNAMES.add("NotoSerifCJKtc-Regular");
+        CJK_FONT_PSNAMES.add("NotoSerifCJKjp-Regular");
+        CJK_FONT_PSNAMES.add("NotoSerifCJKkr-Regular");
+    };
+
+    // Returns list of Noto CJK fonts.
+    private static List<Font> getNotoCJKFonts() {
+        final ArrayList<Font> cjkFonts = new ArrayList<>();
+
+        for (Font font : SystemFonts.getAvailableFonts()) {
+            String psName = FontFileTestUtil.getPostScriptName(font.getFile(), font.getTtcIndex());
+            if (CJK_FONT_PSNAMES.contains(psName)) {
+                cjkFonts.add(font);
+            }
+        }
+
+        return cjkFonts;
+    }
+
+    @Test
+    public void testContextualSpacing() {
+        if (!isCJKSupported()) {
+            return;  // If the device doesn't support CJK language, don't require chws font.
+        }
+
+        Paint paint = new Paint();
+
+        // Only expect chws feature on NotoCJK fonts.
+        getNotoCJKFonts().forEach(font -> {
+            Typeface tf = new Typeface.CustomFallbackBuilder(
+                    new FontFamily.Builder(font).build()
+            ).build();
+
+            paint.setTextSize(100f);  // Make 1em = 100px
+            paint.setTypeface(tf);
+            paint.setFontFeatureSettings("\"chws\" 0");
+
+            PositionedGlyphs offGlyphs = TextRunShaper.shapeTextRun(
+                    TEST_OPEN_DOUBLE_KEY_BRACKET,
+                    0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+                    0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+                    0f, 0f, false /* LTR */, paint);
+
+            paint.setFontFeatureSettings("\"chws\" 1");
+
+            PositionedGlyphs onGlyphs = TextRunShaper.shapeTextRun(
+                    TEST_OPEN_DOUBLE_KEY_BRACKET,
+                    0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+                    0, TEST_OPEN_DOUBLE_KEY_BRACKET.length(),
+                    0f, 0f, false /* LTR */, paint);
+
+            assertThat(onGlyphs.getGlyphX(1)).isLessThan(offGlyphs.getGlyphX(1));
+        });
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
index a0ecfc6..6bf0670 100644
--- a/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
+++ b/tests/tests/text/src/android/text/cts/PrecomputedTextTest.java
@@ -23,6 +23,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
@@ -32,6 +33,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
 import android.text.Layout;
 import android.text.PrecomputedText;
 import android.text.PrecomputedText.Params;
@@ -88,6 +90,14 @@
                 .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
                 .setTextDirection(LTR).build());
+
+        LineBreakConfig lineBreakConfig = new LineBreakConfig();
+        lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+        assertNotNull(new Params.Builder(PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setLineBreakConfig(lineBreakConfig)
+                .setTextDirection(LTR).build());
     }
 
     @Test
@@ -99,6 +109,11 @@
                         .getHyphenationFrequency());
         assertEquals(RTL, new Params.Builder(PAINT).setTextDirection(RTL).build()
                 .getTextDirection());
+
+        LineBreakConfig lineBreakConfig = new LineBreakConfig();
+        lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+        assertTrue(lineBreakConfig.equals(new Params.Builder(PAINT)
+                .setLineBreakConfig(lineBreakConfig).build().getLineBreakConfig()));
     }
 
     @Test
@@ -109,13 +124,20 @@
                      new Params.Builder(PAINT).build().getHyphenationFrequency());
         assertEquals(TextDirectionHeuristics.FIRSTSTRONG_LTR,
                      new Params.Builder(PAINT).build().getTextDirection());
+
+        // Verify that there is no LineBreakConfig instance by default.
+        assertNull(new Params.Builder(PAINT).build().getLineBreakConfig());
     }
 
     @Test
     public void testParams_equals() {
+        LineBreakConfig config = new LineBreakConfig();
+        config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
         final Params base = new Params.Builder(PAINT)
                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setLineBreakConfig(config)
                 .setTextDirection(LTR).build();
 
         assertFalse(base.equals(null));
@@ -125,6 +147,7 @@
         Params other = new Params.Builder(PAINT)
                 .setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NORMAL)
+                .setLineBreakConfig(config)
                 .setTextDirection(LTR).build();
         assertTrue(base.equals(other));
         assertTrue(other.equals(base));
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java
new file mode 100644
index 0000000..641b278
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutBidiTouchTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text.cts;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.platform.test.annotations.AsbSecurityTest;
+import android.text.Layout;
+import android.text.SpannableString;
+import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
+import android.text.TextPaint;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutBidiTouchTest {
+
+    @Test
+    @AsbSecurityTest(cveBugId = 193849901)
+    public void touchOffsetTest() {
+        AssetManager am = InstrumentationRegistry.getInstrumentation().getContext().getAssets();
+        TextPaint p = new TextPaint();
+        Typeface tf = new Typeface.Builder(am, "fonts/StaticLayoutTouchLocation.ttf").build();
+        assertNotNull(tf);
+        p.setTypeface(tf);
+        p.setTextSize(10f);
+
+        CharSequence text = new SpannableString("\u0627\u0020\u2066\u0020\u0061");
+        int width = 10;
+        Layout layout = StaticLayout.Builder.obtain(text, 0, text.length(), p, width)
+                .setTextDirection(TextDirectionHeuristics.RTL)
+                .build();
+
+        // Accessing from left with 20 dividing points.
+        for (int i = 0; i < layout.getLineCount(); ++i) {
+            for (float w = 0; w < layout.getLineWidth(i); w += layout.getLineWidth(i) / 20f) {
+                layout.getOffsetForHorizontal(i, w);
+            }
+        }
+    }
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java
new file mode 100644
index 0000000..8c6d761
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutFallbackLineSpacingTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.text.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.res.AssetManager;
+import android.graphics.Typeface;
+import android.graphics.fonts.Font;
+import android.graphics.fonts.FontFamily;
+import android.graphics.text.PositionedGlyphs;
+import android.graphics.text.TextRunShaper;
+import android.platform.test.annotations.Presubmit;
+import android.text.StaticLayout;
+import android.text.TextPaint;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+/**
+ * Tests StaticLayout vertical metrics behavior.
+ */
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StaticLayoutFallbackLineSpacingTest {
+
+    @Test
+    public void testFallbackSpacing() throws IOException {
+        AssetManager am =
+                InstrumentationRegistry.getInstrumentation().getTargetContext().getAssets();
+        Typeface typeface = new Typeface.CustomFallbackBuilder(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/LowGlyphFont.ttf").build()
+                ).build()
+        ).addCustomFallback(
+                new FontFamily.Builder(
+                        new Font.Builder(am, "fonts/TallGlyphFont.ttf").build()
+                ).build()
+        ).build();
+
+        TextPaint p = new TextPaint();
+        p.setTextSize(100);  // make 1 em = 100 pixels
+        p.setTypeface(typeface);
+
+        PositionedGlyphs glyphs = TextRunShaper.shapeTextRun("a", 0, 1, 0, 1, 0, 0, false, p);
+        // LowGlyphFont has -1 em ascent and 1 em descent.
+        assertThat(glyphs.getAscent()).isEqualTo(-100);
+        assertThat(glyphs.getDescent()).isEqualTo(100);
+
+        glyphs = TextRunShaper.shapeTextRun("\u1000", 0, 1, 0, 1, 0, 0, false, p);
+        // TallGlyphFont has -3 em ascent and 3 em descent.
+        assertThat(glyphs.getAscent()).isEqualTo(-300);
+        assertThat(glyphs.getDescent()).isEqualTo(300);
+
+        String text = "a\u1000";
+        glyphs = TextRunShaper.shapeTextRun(
+                text, 0, text.length(), 0, text.length(), 0, 0, false, p);
+
+        // TallGlyphFont has -3em ascent and 3em descent.
+        assertThat(glyphs.getAscent()).isEqualTo(-300);
+        assertThat(glyphs.getDescent()).isEqualTo(300);
+
+        StaticLayout layout = StaticLayout.Builder.obtain(
+                text, 0, text.length(), p, Integer.MAX_VALUE)
+                .setUseLineSpacingFromFallbacks(true)
+                .setIncludePad(true)
+                .build();
+        assertThat(layout.getLineBottom(0)).isEqualTo(600);
+    }
+
+}
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
index f3781b6..85832fd 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutLineBreakingVariantsTest.java
@@ -20,6 +20,7 @@
 
 import android.content.Context;
 import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
 import android.os.LocaleList;
 import android.text.StaticLayout;
 import android.text.TextPaint;
@@ -47,13 +48,18 @@
         return paint;
     }
 
-    private static StaticLayout buildLayout(String text, LocaleList locales, int width) {
-        return StaticLayout.Builder.obtain(
-                text, 0, text.length(), setupPaint(locales), width).build();
+    private static StaticLayout buildLayout(String text, LocaleList locales,
+            LineBreakConfig lineBreakConfig, int width) {
+        StaticLayout.Builder builder = StaticLayout.Builder.obtain(text, 0, text.length(),
+                setupPaint(locales), width);
+        builder.setLineBreakConfig(lineBreakConfig);
+        return builder.build();
     }
 
-    private static void assertLineBreak(String text, String locale, int width, String... expected) {
-        final StaticLayout layout = buildLayout(text, LocaleList.forLanguageTags(locale), width);
+    private static void assertLineBreak(String text, String locale,
+            LineBreakConfig lineBreakConfig, int width, String... expected) {
+        final StaticLayout layout = buildLayout(text, LocaleList.forLanguageTags(locale),
+                lineBreakConfig, width);
         assertEquals(expected.length, layout.getLineCount());
 
         int currentExpectedOffset = 0;
@@ -78,34 +84,36 @@
 
     @Test
     public void testBreakVariant_loose() {
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 90, SAMPLE_TEXT);
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 80,
+        LineBreakConfig config = new LineBreakConfig();
+        config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 90, SAMPLE_TEXT);
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 80,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC\u30D0",
                 "\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 70,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 70,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 60,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 60,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB",
                 "\u30FC\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 50,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 50,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC",
                 "\u30BB\u30FC\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 40,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 40,
                 "\u30D0\u30C3\u30C6\u30EA",
                 "\u30FC\u30BB\u30FC\u30D0",
                 "\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 30,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 30,
                 "\u30D0\u30C3\u30C6",
                 "\u30EA\u30FC\u30BB",
                 "\u30FC\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 20,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 20,
                 "\u30D0\u30C3",
                 "\u30C6\u30EA",
                 "\u30FC\u30BB",
                 "\u30FC\u30D0",
                 "\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-loose", 10,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 10,
                 "\u30D0",
                 "\u30C3",
                 "\u30C6",
@@ -119,35 +127,37 @@
 
     @Test
     public void testBreakVariant_strict() {
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 90, SAMPLE_TEXT);
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 80,
+        LineBreakConfig config = new LineBreakConfig();
+        config.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 90, SAMPLE_TEXT);
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 80,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 70,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 70,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 60,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 60,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC",
                 "\u30BB\u30FC\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 50,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 50,
                 "\u30D0\u30C3\u30C6\u30EA\u30FC",
                 "\u30BB\u30FC\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 40,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 40,
                 "\u30D0\u30C3\u30C6",
                 "\u30EA\u30FC\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 30,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 30,
                 "\u30D0\u30C3\u30C6",
                 "\u30EA\u30FC",
                 "\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 20,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 20,
                 "\u30D0\u30C3",
                 "\u30C6",
                 "\u30EA\u30FC",
                 "\u30BB\u30FC",
                 "\u30D0\u30FC");
-        assertLineBreak(SAMPLE_TEXT, "ja-JP-u-lb-strict", 10,
+        assertLineBreak(SAMPLE_TEXT, "ja-JP", config, 10,
                 "\u30D0",
                 "\u30C3",
                 "\u30C6",
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index 456ca78..cb3e795 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -32,6 +32,7 @@
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
 import android.os.LocaleList;
 import android.platform.test.annotations.AsbSecurityTest;
 import android.text.Editable;
@@ -238,6 +239,21 @@
             StaticLayout layout = builder.build();
             assertNotNull(layout);
         }
+        {
+            // setLineBreakConfig
+            LineBreakConfig lineBreakConfig = new LineBreakConfig();
+            lineBreakConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
+            StaticLayout.Builder builder = StaticLayout.Builder.obtain(LAYOUT_TEXT, 0,
+                    LAYOUT_TEXT.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH);
+            builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY);
+            builder.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL);
+            builder.setIncludePad(true);
+            builder.setIndents(null, null);
+            builder.setLineBreakConfig(lineBreakConfig);
+            StaticLayout layout = builder.build();
+            assertNotNull(layout);
+        }
     }
 
     @Test
diff --git a/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java b/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
index 723fa27..df4a6d9 100644
--- a/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
+++ b/tests/tests/text/src/android/text/style/cts/StyleSpanTest.java
@@ -19,7 +19,9 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
+import android.content.res.Configuration;
 import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
 import android.os.Parcel;
 import android.text.TextPaint;
 import android.text.style.StyleSpan;
@@ -43,6 +45,8 @@
             p.setDataPosition(0);
             StyleSpan fromParcel = new StyleSpan(p);
             assertEquals(2, fromParcel.getStyle());
+            assertEquals(Configuration.FONT_WEIGHT_ADJUSTMENT_UNDEFINED,
+                    fromParcel.getFontWeightAdjustment());
             new StyleSpan(-2);
         } finally {
             p.recycle();
@@ -59,7 +63,14 @@
     }
 
     @Test
-    public void testUpdateMeasureState() {
+    public void testGetFontWeightAdjustment() {
+        StyleSpan styleSpan = new StyleSpan(2, 300);
+        assertEquals(300, styleSpan.getFontWeightAdjustment());
+    }
+
+
+    @Test
+    public void testUpdateMeasureState_withStyle() {
         StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
 
         TextPaint tp = new TextPaint();
@@ -75,6 +86,24 @@
         assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
     }
 
+    @Test
+    public void testUpdateMeasureState_withFontWeightAdjustment() {
+        StyleSpan styleSpan = new StyleSpan(Typeface.BOLD, 300);
+
+        TextPaint tp = new TextPaint();
+        Typeface tf = Typeface.defaultFromStyle(Typeface.NORMAL);
+        tp.setTypeface(tf);
+
+        assertNotNull(tp.getTypeface());
+        assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
+
+        styleSpan.updateMeasureState(tp);
+
+        assertNotNull(tp.getTypeface());
+        assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
+        assertEquals(tp.getTypeface().getWeight(), FontStyle.FONT_WEIGHT_MAX);
+    }
+
     @Test(expected=NullPointerException.class)
     public void testUpdateMeasureStateNull() {
         StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
@@ -83,7 +112,7 @@
     }
 
     @Test
-    public void testUpdateDrawState() {
+    public void testUpdateDrawState_withStyle() {
         StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
 
         TextPaint tp = new TextPaint();
@@ -99,6 +128,25 @@
         assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
     }
 
+    @Test
+    public void testUpdateDrawState_withFontWeightAdjustment() {
+        StyleSpan styleSpan = new StyleSpan(Typeface.BOLD, 300);
+
+        TextPaint tp = new TextPaint();
+        Typeface tf = Typeface.defaultFromStyle(Typeface.NORMAL);
+        tp.setTypeface(tf);
+
+        assertNotNull(tp.getTypeface());
+        assertEquals(Typeface.NORMAL, tp.getTypeface().getStyle());
+
+        styleSpan.updateDrawState(tp);
+
+        assertNotNull(tp.getTypeface());
+        assertEquals(Typeface.BOLD, tp.getTypeface().getStyle());
+        assertEquals(tp.getTypeface().getWeight(), FontStyle.FONT_WEIGHT_MAX);
+    }
+
+
     @Test(expected=NullPointerException.class)
     public void testUpdateDrawStateNull() {
         StyleSpan styleSpan = new StyleSpan(Typeface.BOLD);
diff --git a/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java b/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java
new file mode 100644
index 0000000..a83e50f
--- /dev/null
+++ b/tests/tests/text/src/android/text/style/cts/SuggestionRangeSpanTest.java
@@ -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.
+ */
+
+package android.text.style.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.text.TextPaint;
+import android.text.style.SuggestionRangeSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link SuggestionRangeSpan}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SuggestionRangeSpanTest {
+    @Test
+    public void testConstructor() {
+        final SuggestionRangeSpan span = new SuggestionRangeSpan();
+        assertEquals(0, span.getBackgroundColor());
+    }
+
+    @Test
+    public void testSerializationDeserialization() {
+        final SuggestionRangeSpan original = new SuggestionRangeSpan();
+        original.setBackgroundColor(1);
+        Parcel parcel = null;
+        final SuggestionRangeSpan clone;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            clone = SuggestionRangeSpan.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+        assertEquals(original.getBackgroundColor(), clone.getBackgroundColor());
+    }
+
+    @Test
+    public void testSetAndGetBackgroundColor() {
+        final SuggestionRangeSpan span = new SuggestionRangeSpan();
+        assertEquals(0, span.getBackgroundColor());
+
+        span.setBackgroundColor(1);
+        assertEquals(1, span.getBackgroundColor());
+    }
+
+    @Test
+    public void testUpdateDrawState() {
+        final SuggestionRangeSpan span = new SuggestionRangeSpan();
+        span.setBackgroundColor(1);
+        TextPaint tp = new TextPaint();
+        span.updateDrawState(tp);
+        assertEquals(1, tp.bgColor);
+    }
+}
+
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
index b88b7d7..6127f10 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/CtsTextClassifierService.java
@@ -52,6 +52,8 @@
     private static final String TAG = "CtsTextClassifierService";
     public static final String MY_PACKAGE = "android.view.textclassifier.cts";
 
+    public static final int DEFAULT_API_WAIT_TIMEOUT = 1_000;
+
     private static final Icon ICON_RES =
             Icon.createWithResource("android", android.R.drawable.btn_star);
     static final Icon ICON_URI =
@@ -64,7 +66,7 @@
 
     private final Map<String, List<TextClassificationSessionId>> mRequestSessions =
             new ArrayMap<>();
-    private final CountDownLatch mRequestLatch = new CountDownLatch(1);
+    private CountDownLatch mRequestLatch = new CountDownLatch(1);
 
     /**
      * Returns the TestWatcher that was used for the testing.
@@ -82,6 +84,14 @@
         return Collections.unmodifiableMap(mRequestSessions);
     }
 
+    void resetRequestLatch(int count) {
+        mRequestLatch = new CountDownLatch(count);
+    }
+
+    void awaitQuery() {
+        awaitQuery(DEFAULT_API_WAIT_TIMEOUT);
+    }
+
     void awaitQuery(long timeoutMillis) {
         try {
             mRequestLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
index 5f18026..bafac32 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassificationTest.java
@@ -42,6 +42,10 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class TextClassificationTest {
@@ -174,14 +178,18 @@
 
     @Test
     public void testTextClassificationRequest() {
+        final ZonedDateTime referenceTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(1000L),
+                ZoneId.of("UTC"));
         final TextClassification.Request request =
                 new TextClassification.Request.Builder(TEXT, START, END)
                         .setDefaultLocales(LOCALES)
+                        .setReferenceTime(referenceTime)
                         .setExtras(BUNDLE)
                         .build();
 
         assertEquals(LOCALES, request.getDefaultLocales());
         assertEquals(BUNDLE_VALUE, request.getExtras().getString(BUNDLE_KEY));
+        assertEquals(referenceTime, request.getReferenceTime());
     }
 
     @Test
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
index a95460b..a732832 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextClassifierServiceSwapTest.java
@@ -21,19 +21,21 @@
 
 import static org.junit.Assume.assumeTrue;
 
-import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
+import android.os.CancellationSignal;
 import android.os.SystemClock;
+import android.service.textclassifier.TextClassifierService;
+import android.view.textclassifier.ConversationActions;
+import android.view.textclassifier.SelectionEvent;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationContext;
 import android.view.textclassifier.TextClassificationManager;
 import android.view.textclassifier.TextClassificationSessionId;
 import android.view.textclassifier.TextClassifier;
-import android.view.textclassifier.TextLanguage;
 import android.view.textclassifier.TextSelection;
 
 import androidx.core.os.BuildCompat;
@@ -50,7 +52,9 @@
 import org.junit.rules.RuleChain;
 import org.junit.runner.RunWith;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Tests for TextClassifierService query related functions.
@@ -94,14 +98,14 @@
         final CtsTextClassifierService service = mTestWatcher.getService();
 
         // Wait a delay for the query is delivered.
-        service.awaitQuery(1_000);
+        service.awaitQuery();
 
         // Verify the request was not passed to the service.
         assertThat(service.getRequestSessions()).isEmpty();
     }
 
     @Test
-    public void multipleActiveSessions() throws Exception {
+    public void testMultipleActiveSessions() throws Exception {
         final TextClassification.Request request =
                 new TextClassification.Request.Builder("Hello World", 0, 1).build();
         final TextClassificationManager tcm =
@@ -113,14 +117,72 @@
                 tcm.createTextClassificationSession(mTextClassificationContext);
 
         firstSession.classifyText(request);
-        secondSessionSession.classifyText(request);
         final CtsTextClassifierService service = mTestWatcher.getService();
-        service.awaitQuery(1_000);
+        service.awaitQuery();
+
+        service.resetRequestLatch(1);
+        secondSessionSession.classifyText(request);
+        service.awaitQuery();
 
         final List<TextClassificationSessionId> sessionIds =
                 service.getRequestSessions().get("onClassifyText");
         assertThat(sessionIds).hasSize(2);
         assertThat(sessionIds.get(0).getValue()).isNotEqualTo(sessionIds.get(1).getValue());
+
+        service.resetRequestLatch(2);
+        firstSession.destroy();
+        secondSessionSession.destroy();
+        service.awaitQuery();
+
+        final List<TextClassificationSessionId> destroyedSessionIds =
+                service.getRequestSessions().get("onDestroyTextClassificationSession");
+        assertThat(destroyedSessionIds).hasSize(2);
+        firstSession.isDestroyed();
+        secondSessionSession.isDestroyed();
+    }
+
+    private void serviceOnSuggestConversationActions(CtsTextClassifierService service)
+            throws Exception {
+        ConversationActions.Request conversationActionRequest =
+                new ConversationActions.Request.Builder(Collections.emptyList()).build();
+        // TODO: add @TestApi for TextClassificationSessionId and use it
+        SelectionEvent event =
+                SelectionEvent.createSelectionStartedEvent(
+                        SelectionEvent.INVOCATION_LINK, 1);
+        final CountDownLatch onSuccessLatch = new CountDownLatch(1);
+        service.onSuggestConversationActions(event.getSessionId(),
+                conversationActionRequest, new CancellationSignal(),
+                new TextClassifierService.Callback<ConversationActions>() {
+                    @Override
+                    public void onFailure(CharSequence charSequence) {
+                        // do nothing
+                    }
+
+                    @Override
+                    public void onSuccess(ConversationActions o) {
+                        onSuccessLatch.countDown();
+                    }
+                });
+        onSuccessLatch.await();
+        final List<TextClassificationSessionId> sessionIds =
+                service.getRequestSessions().get("onSuggestConversationActions");
+        assertThat(sessionIds).hasSize(1);
+    }
+
+    @Test
+    public void testTextClassifierServiceApiCoverage() throws Exception {
+        // Implemented for API test coverage only
+        // Any method that is better tested not be called here.
+        // We already have tests for the TC APIs
+        // We however need to directly query the TextClassifierService methods in the tests for
+        // code coverage.
+        CtsTextClassifierService service = new CtsTextClassifierService();
+
+        service.onConnected();
+
+        serviceOnSuggestConversationActions(service);
+
+        service.onDisconnected();
     }
 
     @Test
diff --git a/tests/tests/time/TEST_MAPPING b/tests/tests/time/TEST_MAPPING
new file mode 100644
index 0000000..9b183e2
--- /dev/null
+++ b/tests/tests/time/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsTimeTestCases",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
\ No newline at end of file
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
index 67c0289..d2f8996 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigKeys.java
@@ -33,53 +33,15 @@
      * Keys and values associated with the location_time_zone_manager.
      * See also {@link LocationTimeZoneManagerShellHelper}.
      */
-    final class LocationTimeZoneManager {
+    public final class LocationTimeZoneManager {
 
         private LocationTimeZoneManager() {
             // No need to instantiate.
         }
 
-        /**
-         * The key for the server flag that can override the device config for whether the primary
-         * location time zone provider is enabled, disabled, or (for testing) in simulation mode.
-         */
-        static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
-                "primary_location_time_zone_provider_mode_override";
-
-        /**
-         * The key for the server flag that can override the device config for whether the secondary
-         * location time zone provider is enabled or disabled, or (for testing) in simulation mode.
-         */
-        static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE =
-                "secondary_location_time_zone_provider_mode_override";
-
-        /**
-         * The "simulated" provider mode.
-         * For use with {@link #KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE} and {@link
-         * #KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE}.
-         */
-        static final String PROVIDER_MODE_SIMULATED = "simulated";
-
-        /**
-         * The "disabled" provider mode (equivalent to there being no provider configured).
-         * For use with {@link #KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE} and {@link
-         * #KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE}.
-         */
-        static final String PROVIDER_MODE_DISABLED = "disabled";
-
-        /**
-         * The key for the server flag that can override the device config for the package name of
-         * the primary provider (when enabled).
-         */
-        static final String KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_PACKAGE_NAME_OVERRIDE =
-                "primary_location_time_zone_provider_package_name_override";
-
-        /**
-         * The key for the server flag that can override the device config for the package name of
-         * the secondary provider (when enabled).
-         */
-        static final String KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_PACKAGE_NAME_OVERRIDE =
-                "secondary_location_time_zone_provider_package_name_override";
+        /** The key for the setting that controls rate limiting of provider events. */
+        public static final String KEY_LTZP_EVENT_FILTERING_AGE_THRESHOLD_MILLIS =
+                "ltzp_event_filtering_age_threshold_millis";
     }
 
     /**
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
index 550daf8..b128a69 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceConfigShellHelper.java
@@ -38,17 +38,20 @@
 public class DeviceConfigShellHelper {
 
     /**
-     * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+     * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+     * {@link #setSyncDisabled(String)}.
      */
     public static final String SYNC_DISABLED_MODE_NONE = "none";
 
     /**
-     * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+     * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+     * {@link #setSyncDisabled(String)}.
      */
     public static final String SYNC_DISABLED_MODE_UNTIL_REBOOT = "until_reboot";
 
     /**
-     * Value used with {@link #setSyncModeForTest}, {@link #setSyncDisabled(String)}.
+     * Value used with {@link #setSyncModeForTest}, {@link #getSyncDisabled()},
+     * {@link #setSyncDisabled(String)}.
      */
     public static final String SYNC_DISABLED_MODE_PERSISTENT = "persistent";
 
@@ -64,11 +67,13 @@
     }
 
     /**
-     * Executes "is_sync_disabled_for_tests". Returns {@code true} or {@code false}.
+     * Executes "get_sync_disabled_for_tests". Returns the output, expected to be one of
+     * {@link #SYNC_DISABLED_MODE_PERSISTENT}, {@link #SYNC_DISABLED_MODE_UNTIL_REBOOT} or
+     * {@link #SYNC_DISABLED_MODE_NONE}.
      */
-    public boolean isSyncDisabled() throws Exception {
-        String cmd = SHELL_CMD_PREFIX + "is_sync_disabled_for_tests";
-        return mShellCommandExecutor.executeToBoolean(cmd);
+    public String getSyncDisabled() throws Exception {
+        String cmd = SHELL_CMD_PREFIX + "get_sync_disabled_for_tests";
+        return mShellCommandExecutor.executeToTrimmedString(cmd);
     }
 
     /**
@@ -135,7 +140,7 @@
             NamespaceEntries namespaceValues = list(namespacetoSave);
             savedValues.add(namespaceValues);
         }
-        PreTestState preTestState = new PreTestState(isSyncDisabled(), savedValues);
+        PreTestState preTestState = new PreTestState(getSyncDisabled(), savedValues);
         setSyncDisabled(syncMode);
         return preTestState;
     }
@@ -155,8 +160,7 @@
                     subMap(oldEntries.keyValues, difference.entriesDiffering().keySet());
             putAll(oldEntries.namespace, entriesToUpdate);
         }
-        setSyncDisabled(restoreState.mIsSyncDisabled
-                ? SYNC_DISABLED_MODE_UNTIL_REBOOT : SYNC_DISABLED_MODE_NONE);
+        setSyncDisabled(restoreState.mSyncDisabledMode);
     }
 
     private static <X, Y> Map<X, Y> subMap(Map<X, Y> keyValues, Set<X> keySet) {
@@ -177,11 +181,11 @@
 
     /** Opaque saved state information. */
     public static class PreTestState {
-        private final boolean mIsSyncDisabled;
+        private final String mSyncDisabledMode;
         private final List<NamespaceEntries> mSavedValues = new ArrayList<>();
 
-        private PreTestState(boolean isSyncDisabled, List<NamespaceEntries> values) {
-            mIsSyncDisabled = isSyncDisabled;
+        private PreTestState(String syncDisabledMode, List<NamespaceEntries> values) {
+            mSyncDisabledMode = syncDisabledMode;
             mSavedValues.addAll(values);
         }
     }
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
index c999181..dc17c63 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/DeviceShellCommandExecutor.java
@@ -86,6 +86,6 @@
      * Converts command line bytes to a String.
      */
     protected static String parseBytesAsString(byte[] result) {
-        return new String(result, 0, result.length, StandardCharsets.ISO_8859_1);
+        return new String(result, 0, result.length, StandardCharsets.UTF_8);
     }
 }
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java
new file mode 100644
index 0000000..2c99860
--- /dev/null
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/FakeTimeZoneProviderAppShellHelper.java
@@ -0,0 +1,190 @@
+/*
+ * 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.app.time.cts.shell;
+
+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 java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * A class for interacting with the fake {@link android.service.timezone.TimeZoneProviderService} in
+ * the fake TimeZoneProviderService app.
+ */
+public final class FakeTimeZoneProviderAppShellHelper {
+
+    /** The name of the app's APK. */
+    public static final String FAKE_TZPS_APP_APK = "CtsFakeTimeZoneProvidersApp.apk";
+
+    /** The package name of the app. */
+    public static final String FAKE_TZPS_APP_PACKAGE = "com.android.time.cts.fake_tzps_app";
+
+    /** The ID of the primary location time zone provider. */
+    public static final String FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID =
+            "FakeLocationTimeZoneProviderService1";
+
+    /** The ID of the secondary location time zone provider. */
+    public static final String FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID =
+                    "FakeLocationTimeZoneProviderService2";
+
+    // The following constant values correspond to enum values from
+    // frameworks/base/core/proto/android/app/location_time_zone_manager.proto
+    public static final int PROVIDER_STATE_UNKNOWN = 0;
+    public static final int PROVIDER_STATE_INITIALIZING = 1;
+    public static final int PROVIDER_STATE_CERTAIN = 2;
+    public static final int PROVIDER_STATE_UNCERTAIN = 3;
+    public static final int PROVIDER_STATE_DISABLED = 4;
+    public static final int PROVIDER_STATE_PERM_FAILED = 5;
+    public static final int PROVIDER_STATE_DESTROYED = 6;
+
+    private static final String METHOD_GET_STATE = "get_state";
+    private static final String CALL_RESULT_KEY_GET_STATE_STATE = "state";
+    private static final String METHOD_REPORT_PERMANENT_FAILURE = "perm_fail";
+    private static final String METHOD_REPORT_UNCERTAIN = "uncertain";
+    private static final String METHOD_REPORT_SUCCESS = "success";
+    private static final String METHOD_PING = "ping";
+
+    /** A single string, comma separated, may be empty. */
+    private static final String CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS = "zone_ids";
+
+    private static final String SHELL_COMMAND_PREFIX = "content ";
+    private static final String AUTHORITY = "faketzpsapp ";
+
+    private final DeviceShellCommandExecutor mShellCommandExecutor;
+
+    public FakeTimeZoneProviderAppShellHelper(DeviceShellCommandExecutor shellCommandExecutor) {
+        mShellCommandExecutor = Objects.requireNonNull(shellCommandExecutor);
+    }
+
+    /**
+     * Throws an exception if the app is not installed / available within a reasonable time.
+     */
+    public void waitForInstallation() throws Exception {
+        long timeoutMs = 10000;
+        long delayUntilMillis = System.currentTimeMillis() + timeoutMs;
+        while (System.currentTimeMillis() <= delayUntilMillis) {
+            try {
+                ping();
+                return;
+            } catch (AssertionError e) {
+                // Not present yet.
+            }
+            Thread.sleep(100);
+        }
+        fail("Installation did not happen in time");
+    }
+
+    public FakeTimeZoneProviderShellHelper getPrimaryLocationProviderHelper() {
+        return getProviderHelper(FAKE_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_ID);
+    }
+
+    public FakeTimeZoneProviderShellHelper getSecondaryLocationProviderHelper() {
+        return getProviderHelper(FAKE_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_ID);
+    }
+
+    private FakeTimeZoneProviderShellHelper getProviderHelper(String providerId) {
+        return new FakeTimeZoneProviderShellHelper(providerId);
+    }
+
+    /**
+     * A helper for interacting with a specific {@link
+     * android.service.timezone.TimeZoneProviderService}.
+     */
+    public final class FakeTimeZoneProviderShellHelper {
+
+        private final String mProviderId;
+
+        private FakeTimeZoneProviderShellHelper(String providerId) {
+            mProviderId = Objects.requireNonNull(providerId);
+        }
+
+        public void reportUncertain() throws Exception {
+            executeContentProviderCall(mProviderId, METHOD_REPORT_UNCERTAIN, null);
+        }
+
+        public void reportPermanentFailure() throws Exception {
+            executeContentProviderCall(mProviderId, METHOD_REPORT_PERMANENT_FAILURE, null);
+        }
+
+        public void reportSuccess(String zoneId) throws Exception {
+            reportSuccess(Collections.singletonList(zoneId));
+        }
+
+        public void reportSuccess(List<String> zoneIds) throws Exception {
+            String zoneIdsExtra = String.join(",", zoneIds);
+            Map<String, String> extras = new HashMap<>();
+            extras.put(CALL_EXTRA_KEY_SUCCESS_SUGGESTION_ZONE_IDS, zoneIdsExtra);
+
+            executeContentProviderCall(mProviderId, METHOD_REPORT_SUCCESS, extras);
+        }
+
+        public int getState() throws Exception {
+            String stateResult = executeContentProviderCall(mProviderId, METHOD_GET_STATE, null);
+            Pattern pattern = Pattern.compile(".*" + CALL_RESULT_KEY_GET_STATE_STATE + "=(.).*");
+            Matcher matcher = pattern.matcher(stateResult);
+            if (!matcher.matches()) {
+                throw new RuntimeException("Unknown result format: " + stateResult);
+            }
+            return Integer.parseInt(matcher.group(1));
+        }
+
+        public void assertCurrentState(int expectedState) throws Exception {
+            assertEquals(expectedState, getState());
+        }
+
+        public boolean exists() throws Exception {
+            try {
+                getState();
+                return true;
+            } catch (AssertionError e) {
+                return false;
+            }
+        }
+
+        public void assertCreated() throws Exception {
+            assertTrue(exists());
+        }
+
+        public void assertNotCreated() throws Exception {
+            assertFalse(exists());
+        }
+    }
+
+    private void ping() throws Exception {
+        String cmd = String.format("call --uri content://%s --method %s", AUTHORITY, METHOD_PING);
+        mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd);
+    }
+
+    private String executeContentProviderCall(
+            String providerId, String method, Map<String, String> extras) throws Exception {
+        String cmd = String.format("call --uri content://%s --method %s --arg %s",
+                AUTHORITY, method, providerId);
+        if (extras != null) {
+            for (Map.Entry<String, String> entry : extras.entrySet()) {
+                cmd += String.format(" --extra %s:s:%s", entry.getKey(), entry.getValue());
+            }
+        }
+        return mShellCommandExecutor.executeToTrimmedString(SHELL_COMMAND_PREFIX + cmd);
+    }
+}
diff --git a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
index 416443c..2417808 100644
--- a/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
+++ b/tests/tests/time/shell_utils/common/android/app/time/cts/shell/LocationTimeZoneManagerShellHelper.java
@@ -15,10 +15,6 @@
  */
 package android.app.time.cts.shell;
 
-import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-import static android.app.time.cts.shell.DeviceConfigKeys.LocationTimeZoneManager.KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-import static android.app.time.cts.shell.DeviceConfigKeys.NAMESPACE_SYSTEM_TIME;
-
 import static org.junit.Assume.assumeTrue;
 
 import java.io.BufferedReader;
@@ -30,47 +26,6 @@
  * command-line interface.
  */
 public class LocationTimeZoneManagerShellHelper {
-    /**
-     * The index of the primary location time zone provider, used for shell commands.
-     */
-    public static final int PRIMARY_PROVIDER_INDEX = 0;
-
-    /**
-     * The index of the secondary location time zone provider, used for shell commands.
-     */
-    public static final int SECONDARY_PROVIDER_INDEX = 1;
-
-    /**
-     * The "disabled" provider mode (equivalent to there being no provider configured).
-     */
-    public static final String PROVIDER_MODE_DISABLED =
-            DeviceConfigKeys.LocationTimeZoneManager.PROVIDER_MODE_DISABLED;
-
-    /**
-     * The "simulated" provider mode.
-     */
-    public static final String PROVIDER_MODE_SIMULATED =
-            DeviceConfigKeys.LocationTimeZoneManager.PROVIDER_MODE_SIMULATED;
-
-    /**
-     * Simulated provider test command that simulates the bind succeeding.
-     */
-    public static final String SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND = "on_bind";
-
-    /**
-     * Simulated provider test command that simulates the provider reporting uncertainty.
-     */
-    public static final String SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN = "uncertain";
-
-    /**
-     * Simulated provider test command that simulates a successful time zone detection.
-     */
-    public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS = "success";
-
-    /**
-     * Argument for {@link #SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS} to specify TZDB time zone IDs.
-     */
-    public static final String SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ = "tz";
 
     /**
      * The name of the service for shell commands.
@@ -88,10 +43,10 @@
     private static final String SHELL_COMMAND_STOP = "stop";
 
     /**
-     * A shell command that tells the service to record state information during tests. The next
-     * argument value is "true" or "false".
+     * A shell command that clears recorded provider state information during tests.
      */
-    private static final String SHELL_COMMAND_RECORD_PROVIDER_STATES = "record_provider_states";
+    private static final String SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES =
+            "clear_recorded_provider_states";
 
     /**
      * A shell command that tells the service to dump its current state.
@@ -103,20 +58,22 @@
      */
     private static final String DUMP_STATE_OPTION_PROTO = "proto";
 
+    /** A shell command that starts the location_time_zone_manager with named test providers. */
+    public static final String SHELL_COMMAND_START_WITH_TEST_PROVIDERS =
+            "start_with_test_providers";
+
     /**
-     * A shell command that sends test commands to a provider
+     * The token that can be passed to {@link #SHELL_COMMAND_START_WITH_TEST_PROVIDERS} to indicate
+     * there is no provider.
      */
-    private static final String SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND =
-            "send_provider_test_command";
+    public static final String NULL_PACKAGE_NAME_TOKEN = "@null";
 
     private static final String SHELL_CMD_PREFIX = "cmd " + SERVICE_NAME + " ";
 
     private final DeviceShellCommandExecutor mShellCommandExecutor;
-    private final DeviceConfigShellHelper mDeviceConfigShellHelper;
 
     public LocationTimeZoneManagerShellHelper(DeviceShellCommandExecutor shellCommandExecutor) {
         mShellCommandExecutor = Objects.requireNonNull(shellCommandExecutor);
-        mDeviceConfigShellHelper = new DeviceConfigShellHelper(shellCommandExecutor);
     }
 
     /**
@@ -156,66 +113,38 @@
         mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + SHELL_COMMAND_STOP);
     }
 
-    /** Executes "record_provider_states". */
-    public void recordProviderStates(boolean enabled) throws Exception {
-        String cmd = String.format("%s %s", SHELL_COMMAND_RECORD_PROVIDER_STATES, enabled);
+    /** Executes "clear_recorded_provider_states". */
+    public void clearRecordedProviderStates() throws Exception {
+        String cmd = SHELL_COMMAND_CLEAR_RECORDED_PROVIDER_STATES;
         mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + cmd);
     }
 
-    /** Executes "dump_state". */
+    /**
+     * Executes "dump_state". Raw proto bytes are returned as host protos tend to use "full" proto,
+     * device protos use "lite".
+     **/
     public byte[] dumpState() throws Exception {
         String cmd = String.format("%s --%s", SHELL_COMMAND_DUMP_STATE, DUMP_STATE_OPTION_PROTO);
         return mShellCommandExecutor.executeToBytes(SHELL_CMD_PREFIX + cmd);
     }
 
-    /** Modifies a provider's mode using "device_config" commands. */
-    public void setProviderModeOverride(int providerIndex, String mode) throws Exception {
-        String deviceConfigKey;
-        if (providerIndex == PRIMARY_PROVIDER_INDEX) {
-            deviceConfigKey = KEY_PRIMARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-        } else {
-            deviceConfigKey = KEY_SECONDARY_LOCATION_TIME_ZONE_PROVIDER_MODE_OVERRIDE;
-        }
-
-        if (mode == null) {
-            mDeviceConfigShellHelper.delete(NAMESPACE_SYSTEM_TIME, deviceConfigKey);
-        } else {
-            mDeviceConfigShellHelper.put(NAMESPACE_SYSTEM_TIME, deviceConfigKey, mode);
-        }
-    }
-
-    /**
-     * Simulates a provider successfully binding using the "send_provider_test_command" command.
-     */
-    public void simulateProviderBind(int providerIndex) throws Exception {
-        sendProviderTestCommand(providerIndex, SIMULATED_PROVIDER_TEST_COMMAND_ON_BIND);
-    }
-
-    /**
-     * Simulates a provider generating an uncertain report using the "send_provider_test_command"
-     * command.
-     */
-    public void simulateProviderUncertain(int providerIndex) throws Exception {
-        sendProviderTestCommand(providerIndex, SIMULATED_PROVIDER_TEST_COMMAND_UNCERTAIN);
-    }
-
-    /**
-     * Simulates a provider generating a suggestion using the "send_provider_test_command" command.
-     */
-    public void simulateProviderSuggestion(int providerIndex, String... zoneIds)
+    /** Executes "start_with_test_providers". */
+    public void startWithTestProviders(String testPrimaryLocationTimeZoneProviderPackageName,
+            String testSecondaryLocationTimeZoneProviderPackageName, boolean recordProviderStates)
             throws Exception {
-        String timeZoneIds = String.join("&", zoneIds);
-        String testCommand = String.format("%s %s=string_array:%s",
-                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS,
-                SIMULATED_PROVIDER_TEST_COMMAND_SUCCESS_ARG_KEY_TZ,
-                timeZoneIds);
-        sendProviderTestCommand(providerIndex, testCommand);
+        testPrimaryLocationTimeZoneProviderPackageName =
+                replaceNullPackageNameWithToken(testPrimaryLocationTimeZoneProviderPackageName);
+        testSecondaryLocationTimeZoneProviderPackageName =
+                replaceNullPackageNameWithToken(testSecondaryLocationTimeZoneProviderPackageName);
+        String cmd = String.format("%s %s %s %s",
+                SHELL_COMMAND_START_WITH_TEST_PROVIDERS,
+                testPrimaryLocationTimeZoneProviderPackageName,
+                testSecondaryLocationTimeZoneProviderPackageName,
+                recordProviderStates);
+        mShellCommandExecutor.executeToBytes(SHELL_CMD_PREFIX + cmd);
     }
 
-    /** Executes "send_provider_test_command". */
-    private void sendProviderTestCommand(int providerIndex, String testCommand) throws Exception {
-        String cmd = String.format("%s %s %s",
-                SHELL_COMMAND_SEND_PROVIDER_TEST_COMMAND, providerIndex, testCommand);
-        mShellCommandExecutor.executeToTrimmedString(SHELL_CMD_PREFIX + cmd);
+    private static String replaceNullPackageNameWithToken(String packageName) {
+        return packageName == null ? NULL_PACKAGE_NAME_TOKEN : packageName;
     }
 }
diff --git a/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java b/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
index 5d7d7ac..2ae437f 100644
--- a/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/device/android/app/time/cts/shell/device/InstrumentationShellCommandExecutor.java
@@ -48,7 +48,7 @@
         byte[] stdOut = readBytesAndClose(parcelFileDescriptors[0]);
         byte[] stdErr = readBytesAndClose(parcelFileDescriptors[2]);
         if (stdErr.length > 0) {
-            fail("Command \'" + command + "\'produced stderr: "
+            fail("Command \'" + command + "\' produced stderr: "
                     + parseBytesAsString(stdErr).trim());
         }
         return stdOut;
diff --git a/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java b/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
index 5356617..85b41aa 100644
--- a/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
+++ b/tests/tests/time/shell_utils/host/android/app/time/cts/shell/host/HostShellCommandExecutor.java
@@ -15,17 +15,24 @@
  */
 package android.app.time.cts.shell.host;
 
+import static org.junit.Assert.fail;
+
 import android.app.time.cts.shell.DeviceShellCommandExecutor;
 
 import androidx.annotation.NonNull;
 
-import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
 
+import java.io.ByteArrayOutputStream;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 public final class HostShellCommandExecutor extends DeviceShellCommandExecutor {
 
+    private static final long MAX_TIMEOUT_FOR_COMMAND_MILLIS = 2 * 60 * 1000;
+    private static final int RETRY_ATTEMPTS = 1;
+
     private final ITestDevice mDevice;
 
     public HostShellCommandExecutor(@NonNull ITestDevice device) {
@@ -35,9 +42,17 @@
     @Override
     @NonNull
     protected byte[] executeToBytesInternal(String command) throws Exception {
-        CollectingByteOutputReceiver bytesReceiver = new CollectingByteOutputReceiver();
-        mDevice.executeShellCommand(command, bytesReceiver);
-        return bytesReceiver.getOutput();
+        ByteArrayOutputStream stdOutBytesReceiver = new ByteArrayOutputStream();
+        ByteArrayOutputStream stdErrBytesReceiver = new ByteArrayOutputStream();
+        CommandResult result = mDevice.executeShellV2Command(
+                command, /*pipeAsInput=*/null, stdOutBytesReceiver, stdErrBytesReceiver,
+                MAX_TIMEOUT_FOR_COMMAND_MILLIS, TimeUnit.MILLISECONDS, RETRY_ATTEMPTS);
+        if (result.getExitCode() != 0 || stdErrBytesReceiver.size() > 0) {
+            fail("Command \'" + command + "\' produced exitCode=" + result.getExitCode()
+                    + " and stderr="
+                    + parseBytesAsString(stdErrBytesReceiver.toByteArray()).trim());
+        }
+        return stdOutBytesReceiver.toByteArray();
     }
 
     @Override
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
index 4195e03..0a26b42 100644
--- a/tests/tests/toast/AndroidTest.xml
+++ b/tests/tests/toast/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for Toast test cases">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="all_foldable_states" />
     <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" />
diff --git a/tests/tests/toastlegacy/AndroidTest.xml b/tests/tests/toastlegacy/AndroidTest.xml
index 95d5a02..2d6c4c6 100644
--- a/tests/tests/toastlegacy/AndroidTest.xml
+++ b/tests/tests/toastlegacy/AndroidTest.xml
@@ -16,6 +16,7 @@
 <configuration description="Config for Toast legacy 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="all_foldable_states" />
     <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
diff --git a/tests/tests/tv/Android.bp b/tests/tests/tv/Android.bp
index f9f2ee4..438393f 100644
--- a/tests/tests/tv/Android.bp
+++ b/tests/tests/tv/Android.bp
@@ -18,7 +18,7 @@
 
 android_library {
     name: "CtsTvTestCases_lib",
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "src/**/*.aidl"],
     libs: [
         "platform-test-annotations",
         "android.test.runner",
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 6f1b399..b79e354 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -46,6 +46,14 @@
             </intent-filter>
         </activity>
 
+        <service android:name="android.media.tv.tuner.cts.SharedFilterTestService"
+             android:process=":SharedFilterTestService"
+             android:exported="true"/>
+
+        <service android:name="android.media.tv.tuner.cts.TunerResourceTestService"
+             android:process=":TunerResourceTestService"
+             android:exported="true"/>
+
         <service android:name="android.media.tv.cts.StubTunerTvInputService"
              android:permission="android.permission.BIND_TV_INPUT"
              android:label="TV input stub"
@@ -128,6 +136,15 @@
                  android:resource="@xml/stub_tv_input_service"/>
         </service>
 
+        <service android:name="android.media.tv.interactive.cts.StubTvIAppService"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.media.tv.interactive.TvIAppService"/>
+            </intent-filter>
+            <meta-data android:name="android.media.tv.interactive.app"
+                 android:resource="@xml/stub_tv_iapp_service"/>
+        </service>
+
         <activity android:name="android.media.tv.cts.TvViewStubActivity"
              android:exported="true">
             <intent-filter>
@@ -142,6 +159,14 @@
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
             </intent-filter>
         </activity>
+
+        <activity android:name="android.media.tv.interactive.cts.TvInteractiveAppViewStubActivity"
+             android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/tv/res/layout/tviappview_layout.xml b/tests/tests/tv/res/layout/tviappview_layout.xml
new file mode 100644
index 0000000..6fc8cf7
--- /dev/null
+++ b/tests/tests/tv/res/layout/tviappview_layout.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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <android.media.tv.interactive.TvInteractiveAppView
+        android:id="@+id/tviappview"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"/>
+</LinearLayout>
diff --git a/tests/tests/tv/res/values/arrays.xml b/tests/tests/tv/res/values/arrays.xml
new file mode 100644
index 0000000..3794d0b
--- /dev/null
+++ b/tests/tests/tv/res/values/arrays.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string-array name="sub_iapp_service_types">
+        <item>ginga</item>
+        <item>hbbtv</item>
+    </string-array>
+</resources>
diff --git a/tests/tests/tv/res/xml/stub_tv_iapp_service.xml b/tests/tests/tv/res/xml/stub_tv_iapp_service.xml
new file mode 100644
index 0000000..bb9747a
--- /dev/null
+++ b/tests/tests/tv/res/xml/stub_tv_iapp_service.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<tv-iapp xmlns:android="http://schemas.android.com/apk/res/android"
+    android:supportedTypes="@array/sub_iapp_service_types"
+    android:canPauseRecording="true" />
diff --git a/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java b/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
index b70dc62..33648cb 100644
--- a/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
+++ b/tests/tests/tv/src/android/media/tv/cts/StubTvInputService.java
@@ -30,7 +30,7 @@
         return new StubSessionImpl(this);
     }
 
-    private static class StubSessionImpl extends Session {
+    public static class StubSessionImpl extends Session {
         StubSessionImpl(Context context) {
             super(context);
         }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
index 5345e42..5134dd3 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputManagerTest.java
@@ -17,6 +17,8 @@
 package android.media.tv.cts;
 
 import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,14 +38,18 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputManager.Hardware;
 import android.media.tv.TvInputManager.HardwareCallback;
+import android.media.tv.TvInputManager.Session;
+import android.media.tv.TvInputManager.SessionCallback;
 import android.media.tv.TvInputService;
 import android.media.tv.TvStreamConfig;
 import android.media.tv.TvView;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.test.ActivityInstrumentationTestCase2;
 import android.tv.cts.R;
 
@@ -66,6 +72,7 @@
 public class TvInputManagerTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
     private static final long TIME_OUT_MS = 15000L;
+    private static final int PRIORITY_HINT_USE_CASE_TYPE_INVALID = 1000;
 
     private static final int DUMMY_DEVICE_ID = Integer.MAX_VALUE;
     private static final String[] VALID_TV_INPUT_SERVICES = {
@@ -654,6 +661,61 @@
         }
     }
 
+    public void testGetClientPriority() {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+
+        // Use the test api to get priorities in tunerResourceManagerUseCaseConfig.xml
+        TunerResourceManager trm = (TunerResourceManager) getActivity()
+            .getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+        int fgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+                true);
+        int bgLivePriority = trm.getConfigPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+                false);
+        int fgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, true);
+        int bgDefaultPriority = trm.getConfigPriority(PRIORITY_HINT_USE_CASE_TYPE_INVALID, false);
+        boolean isForeground = checkIsForeground(android.os.Process.myPid());
+
+        int priority = mManager.getClientPriority(TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE,
+                null);
+        assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority));
+
+        priority = mManager.getClientPriority(
+                PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+                null);
+        assertTrue(priority == (isForeground ? fgDefaultPriority : bgDefaultPriority));
+
+        Handler handler = new Handler(Looper.getMainLooper());
+        final SessionCallback sessionCallback = new SessionCallback();
+        mManager.createSession(mStubId, sessionCallback, handler);
+        PollingCheck.waitFor(TIME_OUT_MS, () -> sessionCallback.getSession() != null);
+        Session session = sessionCallback.getSession();
+        String sessionId = StubTvInputService2.getSessionId();
+        assertNotNull(sessionId);
+
+        priority = mManager.getClientPriority(
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* valid sessionId */);
+        assertTrue(priority == (isForeground ? fgLivePriority : bgLivePriority));
+
+        priority = mManager.getClientPriority(
+                PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+                sessionId /* valid sessionId */);
+        assertTrue(priority == (isForeground ? fgDefaultPriority : fgDefaultPriority));
+
+        session.release();
+        PollingCheck.waitFor(TIME_OUT_MS, () -> StubTvInputService2.getSessionId() == null);
+
+        priority = mManager.getClientPriority(
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE, sessionId /* invalid sessionId */);
+        assertTrue(priority == bgLivePriority);
+
+        priority = mManager.getClientPriority(
+                PRIORITY_HINT_USE_CASE_TYPE_INVALID /* invalid use case type */,
+                sessionId /* invalid sessionId */);
+        assertTrue(priority == bgDefaultPriority);
+    }
+
     private static class LoggingCallback extends TvInputManager.TvInputCallback {
         private final List<String> mAddedInputs = new ArrayList<>();
         private final List<String> mRemovedInputs = new ArrayList<>();
@@ -704,9 +766,27 @@
     }
 
     public static class StubTvInputService2 extends StubTvInputService {
+        static String sTvInputSessionId;
+
+        public static String getSessionId() {
+            return sTvInputSessionId;
+        }
+
         @Override
-        public Session onCreateSession(String inputId) {
-            return null;
+        public Session onCreateSession(String inputId, String tvInputSessionId) {
+            sTvInputSessionId = tvInputSessionId;
+            return new StubSessionImpl2(this);
+        }
+
+        public static class StubSessionImpl2 extends StubTvInputService.StubSessionImpl {
+            StubSessionImpl2(Context context) {
+                super(context);
+            }
+
+            @Override
+            public void onRelease() {
+                sTvInputSessionId = null;
+            }
         }
     }
 
@@ -784,4 +864,33 @@
             r.run();
         }
     }
+
+    private class SessionCallback extends TvInputManager.SessionCallback {
+        private TvInputManager.Session mSession;
+
+        public TvInputManager.Session getSession() {
+            return mSession;
+        }
+
+        @Override
+        public void onSessionCreated(TvInputManager.Session session) {
+            mSession = session;
+        }
+    }
+
+    private boolean checkIsForeground(int pid) {
+        ActivityManager am = (ActivityManager) getActivity()
+            .getSystemService(Context.ACTIVITY_SERVICE);
+        List<RunningAppProcessInfo> appProcesses = am.getRunningAppProcesses();
+        if (appProcesses == null) {
+            return false;
+        }
+        for (RunningAppProcessInfo appProcess : appProcesses) {
+            if (appProcess.pid == pid
+                    && appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java b/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java
new file mode 100644
index 0000000..fc11f2c
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/StubTvIAppService.java
@@ -0,0 +1,74 @@
+/*
+ * 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.media.tv.interactive.cts;
+
+import android.content.Context;
+import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.TvIAppService;
+import android.view.KeyEvent;
+import android.view.Surface;
+
+/**
+ * Stub implementation of (@link android.media.tv.interactive.TvIAppService}.
+ */
+public class StubTvIAppService extends TvIAppService {
+    @Override
+    public Session onCreateSession(String iAppServiceId, int type) {
+        Session session = new StubSessionImpl(this);
+        return session;
+    }
+
+    private static class StubSessionImpl extends Session {
+        StubSessionImpl(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStartInteractiveApp() {
+            notifySessionStateChanged(TvIAppManager.TV_INTERACTIVE_APP_RTE_STATE_READY);
+        }
+
+        @Override
+        public void onRelease() {
+        }
+
+        @Override
+        public boolean onSetSurface(Surface surface) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            return false;
+        }
+
+        @Override
+        public boolean onKeyMultiple(int keyCode, int count, KeyEvent event) {
+            return false;
+        }
+    }
+}
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java
new file mode 100644
index 0000000..5d4a96a
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewStubActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.media.tv.interactive.cts;
+
+import android.app.Activity;
+import android.media.tv.interactive.TvInteractiveAppView;
+import android.os.Bundle;
+import android.tv.cts.R;
+
+public class TvInteractiveAppViewStubActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.tviappview_layout);
+    }
+
+    public TvInteractiveAppView getTvInteractiveAppView() {
+        return findViewById(R.id.tviappview);
+    }
+}
diff --git a/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java
new file mode 100644
index 0000000..2bcf440
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/interactive/cts/TvInteractiveAppViewTest.java
@@ -0,0 +1,176 @@
+/*
+ * 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.media.tv.interactive.cts;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.tv.interactive.TvIAppManager;
+import android.media.tv.interactive.TvInteractiveAppInfo;
+import android.media.tv.interactive.TvInteractiveAppView;
+import android.os.ConditionVariable;
+import android.tv.cts.R;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.RequiredFeatureRule;
+
+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.Executor;
+
+/**
+ * Test {@link android.media.tv.interactive.TvInteractiveAppView}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class TvInteractiveAppViewTest {
+    private static final long TIME_OUT_MS = 20000L;
+
+    private Instrumentation mInstrumentation;
+    private ActivityScenario<TvInteractiveAppViewStubActivity> mActivityScenario;
+    private TvInteractiveAppViewStubActivity mActivity;
+    private TvInteractiveAppView mTvInteractiveAppView;
+    private TvIAppManager mManager;
+    private TvInteractiveAppInfo mStubInfo;
+
+    @Rule
+    public RequiredFeatureRule featureRule = new RequiredFeatureRule(
+            PackageManager.FEATURE_LIVE_TV);
+
+    private final MockCallback mCallback = new MockCallback();
+
+    public static class MockCallback extends TvInteractiveAppView.TvInteractiveAppCallback {
+        private String mIAppServiceId;
+        private int mState = -1;
+
+        @Override
+        public void onSessionStateChanged(String iAppServiceId, int state) {
+            mIAppServiceId = iAppServiceId;
+            mState = state;
+        }
+    }
+
+    private TvInteractiveAppView findTvInteractiveAppViewById(int id) {
+        return (TvInteractiveAppView) mActivity.findViewById(id);
+    }
+
+    private void runTestOnUiThread(final Runnable r) throws Throwable {
+        final Throwable[] exceptions = new Throwable[1];
+        mInstrumentation.runOnMainSync(new Runnable() {
+            public void run() {
+                try {
+                    r.run();
+                } catch (Throwable throwable) {
+                    exceptions[0] = throwable;
+                }
+            }
+        });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+
+    private Executor getExecutor() {
+        return Runnable::run;
+    }
+
+    @Before
+    public void setUp() throws Throwable {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setClass(
+                mInstrumentation.getTargetContext(), TvInteractiveAppViewStubActivity.class);
+
+        // DO NOT use ActivityScenario.launch(Class), which can cause ActivityNotFoundException
+        // related to BootstrapActivity.
+        mActivityScenario = ActivityScenario.launch(intent);
+        ConditionVariable activityReferenceObtained = new ConditionVariable();
+        mActivityScenario.onActivity(activity -> {
+            mActivity = activity;
+            activityReferenceObtained.open();
+        });
+        activityReferenceObtained.block(TIME_OUT_MS);
+
+        assertNotNull("Failed to acquire activity reference.", mActivity);
+        mTvInteractiveAppView = findTvInteractiveAppViewById(R.id.tviappview);
+        assertNotNull("Failed to find TvInteractiveAppView.", mTvInteractiveAppView);
+
+        mManager = (TvIAppManager) mActivity.getSystemService(Context.TV_IAPP_SERVICE);
+        assertNotNull("Failed to get TvIAppManager.", mManager);
+
+        for (TvInteractiveAppInfo info : mManager.getTvInteractiveAppServiceList()) {
+            if (info.getServiceInfo().name.equals(StubTvIAppService.class.getName())) {
+                mStubInfo = info;
+            }
+        }
+        assertNotNull(mStubInfo);
+        mTvInteractiveAppView.setCallback(mCallback, getExecutor());
+    }
+
+    @After
+    public void tearDown() throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mTvInteractiveAppView.reset();
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        mActivity = null;
+        mActivityScenario.close();
+    }
+
+    @Test
+    public void testConstructor() throws Throwable {
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                new TvInteractiveAppView(mActivity);
+                new TvInteractiveAppView(mActivity, null);
+                new TvInteractiveAppView(mActivity, null, 0);
+            }
+        });
+    }
+
+    @Test
+    public void testStartInteractiveApp() throws Throwable {
+        mTvInteractiveAppView.prepareInteractiveApp(mStubInfo.getId(), 1);
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mTvInteractiveAppView.getInteractiveAppSession() != null;
+            }
+        }.run();
+        mTvInteractiveAppView.startInteractiveApp();
+        new PollingCheck(TIME_OUT_MS) {
+            @Override
+            protected boolean check() {
+                return mCallback.mIAppServiceId == mStubInfo.getId()
+                        && mCallback.mState == TvIAppManager.TV_INTERACTIVE_APP_RTE_STATE_READY;
+            }
+        }.run();
+    }
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl b/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl
new file mode 100644
index 0000000..2bf73a4
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/ISharedFilterTestServer.aidl
@@ -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 android.media.tv.tuner.cts;
+
+interface ISharedFilterTestServer {
+    String acquireSharedFilterToken();
+    void closeFilter();
+    void freeSharedFilterToken(String token);
+    boolean verifySharedFilter(String token);
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl b/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl
new file mode 100644
index 0000000..199f0c0
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/ITunerResourceTestServer.aidl
@@ -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.media.tv.tuner.cts;
+
+interface ITunerResourceTestServer {
+    void createTuner(int useCase);
+    int tune(int frontendIndex);
+    oneway void tuneAsync(int frontendIndex);
+    void closeTuner();
+    boolean verifyTunerIsNull();
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java b/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java
new file mode 100644
index 0000000..1ce51e3
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/SharedFilterTestService.java
@@ -0,0 +1,141 @@
+/*
+ * 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.media.tv.tuner.cts;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.cts.ISharedFilterTestServer;
+import android.media.tv.tuner.filter.Filter;
+import android.media.tv.tuner.filter.FilterCallback;
+import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.SharedFilter;
+import android.media.tv.tuner.filter.SharedFilterCallback;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class SharedFilterTestService extends Service {
+    private static final String TAG = "SharedFilterTestService";
+    private Context mContext = null;
+    private Tuner mTuner = null;
+    private Filter mFilter = null;
+    private boolean mTuning = false;
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mContext = this;
+        mTuner = new Tuner(mContext, null, 100);
+        return new SharedFilterTestServer();
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        mTuner.close();
+        mTuner = null;
+        return false;
+    }
+
+    private class SharedFilterTestServer extends ISharedFilterTestServer.Stub {
+        @Override
+        public String acquireSharedFilterToken() {
+            mFilter = TunerTest.createTsSectionFilter(
+                    mTuner, getExecutor(), getFilterCallback());
+
+            // Tune a frontend before start the filter
+            List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+            mTuner.tune(TunerTest.createFrontendSettings(infos.get(0)));
+            mTuning = true;
+
+            return mFilter.acquireSharedFilterToken();
+        }
+
+        @Override
+        public void closeFilter() {
+            if (mTuning) {
+                mTuner.cancelTuning();
+                mTuning = false;
+            }
+            mFilter.close();
+            mFilter = null;
+        }
+
+        @Override
+        public void freeSharedFilterToken(String token) {
+            if (mTuning) {
+                mTuner.cancelTuning();
+                mTuning = false;
+            }
+            mFilter.freeSharedFilterToken(token);
+        }
+
+        @Override
+        public boolean verifySharedFilter(String token) {
+            SharedFilter f = Tuner.openSharedFilter(
+                    mContext, token, getExecutor(), getSharedFilterCallback());
+            if (f == null) {
+                Log.e(TAG, "SharedFilter is null");
+                return false;
+            }
+            if (f.start() != Tuner.RESULT_SUCCESS) {
+                f = null;
+                Log.e(TAG, "Failed to start SharedFilter");
+                return false;
+            }
+            if (f.flush() != Tuner.RESULT_SUCCESS) {
+                f.close();
+                f = null;
+                Log.e(TAG, "Failed to flush SharedFilter");
+                return false;
+            }
+            f.read(new byte[3], 0, 3);
+            if (f.stop() != Tuner.RESULT_SUCCESS) {
+                f.close();
+                f = null;
+                Log.e(TAG, "Failed to stop SharedFilter");
+                return false;
+            }
+            f.close();
+            f = null;
+            return true;
+        }
+    }
+
+    private FilterCallback getFilterCallback() {
+        return new FilterCallback() {
+            @Override
+            public void onFilterEvent(Filter filter, FilterEvent[] events) {}
+            @Override
+            public void onFilterStatusChanged(Filter filter, int status) {}
+        };
+    }
+
+    private SharedFilterCallback getSharedFilterCallback() {
+        return new SharedFilterCallback() {
+            @Override
+            public void onFilterEvent(SharedFilter filter, FilterEvent[] events) {}
+            @Override
+            public void onFilterStatusChanged(SharedFilter filter, int status) {}
+        };
+    }
+
+    private Executor getExecutor() { return Runnable::run; }
+}
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
index 834acad..8d47bb6 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerDvrTest.java
@@ -160,6 +160,13 @@
         d.flush();
         assertEquals(3, d.read(3));
         assertEquals(3, d.read(new byte[3], 0, 3));
+        assertEquals(0, d.seek(0));
+        assertEquals(3, d.read(3));
+        assertEquals(3, d.read(new byte[3], 0, 3));
+        assertEquals(5, d.seek(5));
+        assertEquals(2, d.read(3));
+        assertEquals(10, d.seek(10));
+        assertEquals(0, d.read(3));
         d.stop();
         d.close();
 
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
index bde450f..3f4b683 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFilterTest.java
@@ -85,6 +85,7 @@
                 AvSettings
                         .builder(Filter.TYPE_TS, true) // is Audio
                         .setPassthrough(false)
+                        .setUseSecureMemory(false)
                         .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                         .build();
 
@@ -94,10 +95,14 @@
         } else {
             assertEquals(settings.getAudioStreamType(), AvSettings.AUDIO_STREAM_TYPE_UNDEFINED);
         }
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(settings.useSecureMemory(), false);
+        }
 
         settings = AvSettings
                 .builder(Filter.TYPE_TS, false) // is Video
                 .setPassthrough(false)
+                .setUseSecureMemory(true)
                 .setVideoStreamType(AvSettings.VIDEO_STREAM_TYPE_MPEG1)
                 .build();
 
@@ -107,6 +112,9 @@
         } else {
             assertEquals(settings.getVideoStreamType(), AvSettings.VIDEO_STREAM_TYPE_UNDEFINED);
         }
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(settings.useSecureMemory(), true);
+        }
     }
 
     @Test
@@ -193,6 +201,48 @@
     }
 
     @Test
+    public void testMmtpSectionSettingsWithSectionBits() throws Exception {
+        SectionSettingsWithSectionBits settings =
+                SectionSettingsWithSectionBits.builder(Filter.TYPE_MMTP)
+                        .setCrcEnabled(true)
+                        .setBitWidthOfLengthField(16)
+                        .setRepeat(false)
+                        .setRaw(false)
+                        .setFilter(new byte[] {2, 3, 4})
+                        .setMask(new byte[] {7, 6, 5, 4})
+                        .setMode(new byte[] {22, 55, 33})
+                        .build();
+
+        assertTrue(settings.isCrcEnabled());
+        assertFalse(settings.isRepeat());
+        assertFalse(settings.isRaw());
+        assertEquals(settings.getBitWidthOfLengthField(), 16);
+        Assert.assertArrayEquals(new byte[] {2, 3, 4}, settings.getFilterBytes());
+        Assert.assertArrayEquals(new byte[] {7, 6, 5, 4}, settings.getMask());
+        Assert.assertArrayEquals(new byte[] {22, 55, 33}, settings.getMode());
+    }
+
+    @Test
+    public void testMMtpSectionSettingsWithTableInfo() throws Exception {
+        SectionSettingsWithTableInfo settings =
+                SectionSettingsWithTableInfo.builder(Filter.TYPE_MMTP)
+                        .setTableId(11)
+                        .setVersion(2)
+                        .setCrcEnabled(true)
+                        .setBitWidthOfLengthField(32)
+                        .setRepeat(true)
+                        .setRaw(true)
+                        .build();
+
+        assertEquals(11, settings.getTableId());
+        assertEquals(2, settings.getVersion());
+        assertTrue(settings.isCrcEnabled());
+        assertEquals(settings.getBitWidthOfLengthField(), 32);
+        assertTrue(settings.isRepeat());
+        assertTrue(settings.isRaw());
+    }
+
+    @Test
     public void testAlpFilterConfiguration() throws Exception {
         AlpFilterConfiguration config =
                 AlpFilterConfiguration
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
index 9bd036a..2a923fe 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerFrontendTest.java
@@ -99,7 +99,7 @@
     }
 
     @Test
-    public void testAnalogFrontendSettings() throws Exception {
+    public void testAnalogFrontendSettingsWithIntFrequency() throws Exception {
         AnalogFrontendSettings settings =
                 AnalogFrontendSettings
                         .builder()
@@ -136,7 +136,7 @@
     }
 
     @Test
-    public void testAtsc3FrontendSettings() throws Exception {
+    public void testAtsc3FrontendSettingsWithIntFrequency() throws Exception {
         Atsc3PlpSettings plp1 =
                 Atsc3PlpSettings
                         .builder()
@@ -201,7 +201,7 @@
     }
 
     @Test
-    public void testAtscFrontendSettings() throws Exception {
+    public void testAtscFrontendSettingsWithIntFrequency() throws Exception {
         AtscFrontendSettings settings =
                 AtscFrontendSettings
                         .builder()
@@ -227,7 +227,7 @@
     }
 
     @Test
-    public void testDvbcFrontendSettings() throws Exception {
+    public void testDvbcFrontendSettingsWithIntFrequency() throws Exception {
         DvbcFrontendSettings settings =
                 DvbcFrontendSettings
                         .builder()
@@ -267,7 +267,7 @@
     }
 
     @Test
-    public void testDvbsFrontendSettings() throws Exception {
+    public void testDvbsFrontendSettingsWithIntFrequency() throws Exception {
         DvbsCodeRate codeRate =
                 DvbsCodeRate
                         .builder()
@@ -328,7 +328,7 @@
     }
 
     @Test
-    public void testDvbtFrontendSettings() throws Exception {
+    public void testDvbtFrontendSettingsWithIntFrequency() throws Exception {
         DvbtFrontendSettings settings =
                 DvbtFrontendSettings
                         .builder()
@@ -382,7 +382,7 @@
     }
 
     @Test
-    public void testIsdbs3FrontendSettings() throws Exception {
+    public void testIsdbs3FrontendSettingsWithIntFrequency() throws Exception {
         Isdbs3FrontendSettings settings =
                 Isdbs3FrontendSettings
                         .builder()
@@ -418,7 +418,7 @@
     }
 
     @Test
-    public void testIsdbsFrontendSettings() throws Exception {
+    public void testIsdbsFrontendSettingsWithIntFrequency() throws Exception {
         IsdbsFrontendSettings settings =
                 IsdbsFrontendSettings
                         .builder()
@@ -455,28 +455,46 @@
     }
 
     @Test
-    public void testIsdbtFrontendSettings() throws Exception {
-        IsdbtFrontendSettings settings =
-                IsdbtFrontendSettings
-                        .builder()
-                        .setFrequency(9)
-                        .setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM)
-                        .setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ)
-                        .setMode(IsdbtFrontendSettings.MODE_2)
-                        .setCodeRate(DvbtFrontendSettings.CODERATE_7_8)
-                        .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4)
-                        .setServiceAreaId(10)
-                        .build();
+    public void testIsdbtFrontendSettingsWithIntFrequency() throws Exception {
+        IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+        builder.setFrequency(9);
+        builder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM);
+        builder.setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ);
+        builder.setMode(IsdbtFrontendSettings.MODE_2);
+        builder.setCodeRate(DvbtFrontendSettings.CODERATE_7_8);
+        builder.setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4);
+        builder.setServiceAreaId(10);
 
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+                    IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+            layerBuilder.setTimeInterleaveMode(IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            layerBuilder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            layerBuilder.setCodeRate(DvbtFrontendSettings.CODERATE_5_6);
+            layerBuilder.setNumberOfSegments(0xFF);
+            IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+            builder.setLayerSettings(new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer, layer});
+            builder.setPartialReceptionFlag(IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+        }
+
+        IsdbtFrontendSettings settings = builder.build();
         settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
         settings.setEndFrequency(100);
 
         assertEquals(FrontendSettings.TYPE_ISDBT, settings.getType());
         assertEquals(9, settings.getFrequency());
-        assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(IsdbtFrontendSettings.MODULATION_UNDEFINED, settings.getModulation());
+        } else {
+            assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+        }
         assertEquals(IsdbtFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
         assertEquals(IsdbtFrontendSettings.MODE_2, settings.getMode());
-        assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(DvbtFrontendSettings.CODERATE_UNDEFINED, settings.getCodeRate());
+        } else {
+            assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+        }
         assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_1_4, settings.getGuardInterval());
         assertEquals(10, settings.getServiceAreaId());
         if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
@@ -488,10 +506,27 @@
                     settings.getFrontendSpectralInversion());
             assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequency());
         }
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            IsdbtFrontendSettings.IsdbtLayerSettings[] layers = settings.getLayerSettings();
+            assertEquals(layers.length, 2);
+            assertEquals(layers[0].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            assertEquals(layers[0].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+            assertEquals(layers[0].getTimeInterleaveMode(),
+                    IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            assertEquals(layers[0].getNumberOfSegments(), 0xFF);
+            assertEquals(layers[1].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            assertEquals(layers[1].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+            assertEquals(layers[1].getTimeInterleaveMode(),
+                    IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            assertEquals(layers[1].getNumberOfSegments(), 0xFF);
+            assertEquals(settings.getPartialReceptionFlag(),
+                    IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+        }
     }
 
     @Test
-    public void testDtmbFrontendSettings() throws Exception {
+    public void testDtmbFrontendSettingsWithIntFrequency() throws Exception {
         if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
                 TAG + ": testDtmbFrontendSettings")) {
             return;
@@ -521,8 +556,9 @@
     }
 
     @Test
-    public void testFrontendInfo() throws Exception {
+    public void testFrontendInfoWithIntFrequency() throws Exception {
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
         Map<Integer, FrontendInfo> infoMap = new HashMap<>();
         for (FrontendInfo info : infos) {
@@ -583,6 +619,526 @@
         assertTrue(infoMap.isEmpty());
     }
 
+    @Test
+    public void testAnalogFrontendSettingsWithLongFrequency() throws Exception {
+        AnalogFrontendSettings settings =
+                AnalogFrontendSettings
+                        .builder()
+                        .setFrequencyLong(1)
+                        .setSignalType(AnalogFrontendSettings.SIGNAL_TYPE_NTSC)
+                        .setSifStandard(AnalogFrontendSettings.SIF_BG_NICAM)
+                        .setAftFlag(AnalogFrontendSettings.AFT_FLAG_TRUE)
+                        .build();
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+            settings.setEndFrequencyLong(100);
+        } else {
+            settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+            settings.setEndFrequencyLong(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY);
+        }
+
+        assertEquals(FrontendSettings.TYPE_ANALOG, settings.getType());
+        assertEquals(1, settings.getFrequencyLong());
+        assertEquals(AnalogFrontendSettings.SIGNAL_TYPE_NTSC, settings.getSignalType());
+        assertEquals(AnalogFrontendSettings.SIF_BG_NICAM, settings.getSifStandard());
+        assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(AnalogFrontendSettings.AFT_FLAG_TRUE, settings.getAftFlag());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(AnalogFrontendSettings.AFT_FLAG_UNDEFINED, settings.getAftFlag());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testAtsc3FrontendSettingsWithLongFrequency() throws Exception {
+        Atsc3PlpSettings plp1 =
+                Atsc3PlpSettings
+                        .builder()
+                        .setPlpId(1)
+                        .setModulation(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+                        .setInterleaveMode(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_AUTO)
+                        .setCodeRate(Atsc3FrontendSettings.CODERATE_6_15)
+                        .setFec(Atsc3FrontendSettings.FEC_BCH_LDPC_64K)
+                        .build();
+
+        Atsc3PlpSettings plp2 =
+                Atsc3PlpSettings
+                        .builder()
+                        .setPlpId(2)
+                        .setModulation(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+                        .setInterleaveMode(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_HTI)
+                        .setCodeRate(Atsc3FrontendSettings.CODERATE_UNDEFINED)
+                        .setFec(Atsc3FrontendSettings.FEC_LDPC_16K)
+                        .build();
+
+        Atsc3FrontendSettings settings =
+                Atsc3FrontendSettings
+                        .builder()
+                        .setFrequencyLong(2)
+                        .setBandwidth(Atsc3FrontendSettings.BANDWIDTH_BANDWIDTH_6MHZ)
+                        .setDemodOutputFormat(Atsc3FrontendSettings.MODULATION_MOD_QPSK)
+                        .setPlpSettings(new Atsc3PlpSettings[] {plp1, plp2})
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_ATSC3, settings.getType());
+        assertEquals(2, settings.getFrequencyLong());
+        assertEquals(Atsc3FrontendSettings.BANDWIDTH_BANDWIDTH_6MHZ, settings.getBandwidth());
+        assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, settings.getDemodOutputFormat());
+
+        Atsc3PlpSettings[] plps = settings.getPlpSettings();
+        assertEquals(2, plps.length);
+
+        assertEquals(1, plps[0].getPlpId());
+        assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, plps[0].getModulation());
+        assertEquals(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_AUTO, plps[0].getInterleaveMode());
+        assertEquals(Atsc3FrontendSettings.CODERATE_6_15, plps[0].getCodeRate());
+        assertEquals(Atsc3FrontendSettings.FEC_BCH_LDPC_64K, plps[0].getFec());
+
+        assertEquals(2, plps[1].getPlpId());
+        assertEquals(Atsc3FrontendSettings.MODULATION_MOD_QPSK, plps[1].getModulation());
+        assertEquals(Atsc3FrontendSettings.TIME_INTERLEAVE_MODE_HTI, plps[1].getInterleaveMode());
+        assertEquals(Atsc3FrontendSettings.CODERATE_UNDEFINED, plps[1].getCodeRate());
+        assertEquals(Atsc3FrontendSettings.FEC_LDPC_16K, plps[1].getFec());
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testAtscFrontendSettingsWithLongFrequency() throws Exception {
+        AtscFrontendSettings settings =
+                AtscFrontendSettings
+                        .builder()
+                        .setFrequencyLong(3)
+                        .setModulation(AtscFrontendSettings.MODULATION_MOD_8VSB)
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_ATSC, settings.getType());
+        assertEquals(3, settings.getFrequencyLong());
+        assertEquals(AtscFrontendSettings.MODULATION_MOD_8VSB, settings.getModulation());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testDvbcFrontendSettingsWithLongFrequency() throws Exception {
+        DvbcFrontendSettings settings =
+                DvbcFrontendSettings
+                        .builder()
+                        .setFrequencyLong(4)
+                        .setModulation(DvbcFrontendSettings.MODULATION_MOD_32QAM)
+                        .setInnerFec(FrontendSettings.FEC_8_15)
+                        .setSymbolRate(3)
+                        .setOuterFec(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS)
+                        .setAnnex(DvbcFrontendSettings.ANNEX_B)
+                        .setTimeInterleaveMode(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO)
+                        .setBandwidth(DvbcFrontendSettings.BANDWIDTH_5MHZ)
+                        // DvbcFrontendSettings.SpectralInversion is deprecated in Android 12. Use
+                        // FrontendSettings.FrontendSpectralInversion instead.
+                        .setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL)
+                        .build();
+
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_DVBC, settings.getType());
+        assertEquals(4, settings.getFrequencyLong());
+        assertEquals(DvbcFrontendSettings.MODULATION_MOD_32QAM, settings.getModulation());
+        assertEquals(FrontendSettings.FEC_8_15, settings.getInnerFec());
+        assertEquals(3, settings.getSymbolRate());
+        assertEquals(DvbcFrontendSettings.OUTER_FEC_OUTER_FEC_RS, settings.getOuterFec());
+        assertEquals(DvbcFrontendSettings.ANNEX_B, settings.getAnnex());
+        assertEquals(DvbcFrontendSettings.TIME_INTERLEAVE_MODE_AUTO,
+                settings.getTimeInterleaveMode());
+        assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                settings.getSpectralInversion());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(100, settings.getEndFrequencyLong());
+            assertEquals(DvbcFrontendSettings.BANDWIDTH_5MHZ, settings.getBandwidth());
+        } else {
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+            assertEquals(DvbcFrontendSettings.BANDWIDTH_UNDEFINED, settings.getBandwidth());
+        }
+    }
+
+    @Test
+    public void testDvbsFrontendSettingsWithLongFrequency() throws Exception {
+        DvbsCodeRate codeRate =
+                DvbsCodeRate
+                        .builder()
+                        .setInnerFec(FrontendSettings.FEC_9_10)
+                        .setLinear(true)
+                        .setShortFrameEnabled(false)
+                        .setBitsPer1000Symbol(55)
+                        .build();
+
+        DvbsFrontendSettings settings =
+                DvbsFrontendSettings
+                        .builder()
+                        .setFrequencyLong(5)
+                        .setModulation(DvbsFrontendSettings.MODULATION_MOD_ACM)
+                        .setCodeRate(codeRate)
+                        .setSymbolRate(3)
+                        .setRolloff(DvbsFrontendSettings.ROLLOFF_0_15)
+                        .setPilot(DvbsFrontendSettings.PILOT_OFF)
+                        .setInputStreamId(1)
+                        .setStandard(DvbsFrontendSettings.STANDARD_S2)
+                        .setVcmMode(DvbsFrontendSettings.VCM_MODE_MANUAL)
+                        .setScanType(DvbsFrontendSettings.SCAN_TYPE_DIRECT)
+                        .setCanHandleDiseqcRxMessage(true)
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_DVBS, settings.getType());
+        assertEquals(5, settings.getFrequencyLong());
+        assertEquals(DvbsFrontendSettings.MODULATION_MOD_ACM, settings.getModulation());
+        assertEquals(3, settings.getSymbolRate());
+        assertEquals(DvbsFrontendSettings.ROLLOFF_0_15, settings.getRolloff());
+        assertEquals(DvbsFrontendSettings.PILOT_OFF, settings.getPilot());
+        assertEquals(1, settings.getInputStreamId());
+        assertEquals(DvbsFrontendSettings.STANDARD_S2, settings.getStandard());
+        assertEquals(DvbsFrontendSettings.VCM_MODE_MANUAL, settings.getVcmMode());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(DvbsFrontendSettings.SCAN_TYPE_DIRECT, settings.getScanType());
+            assertTrue(settings.canHandleDiseqcRxMessage());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(DvbsFrontendSettings.SCAN_TYPE_UNDEFINED, settings.getScanType());
+            assertFalse(settings.canHandleDiseqcRxMessage());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+
+        DvbsCodeRate cr = settings.getCodeRate();
+        assertNotNull(cr);
+        assertEquals(FrontendSettings.FEC_9_10, cr.getInnerFec());
+        assertEquals(true, cr.isLinear());
+        assertEquals(false, cr.isShortFrameEnabled());
+        assertEquals(55, cr.getBitsPer1000Symbol());
+    }
+
+    @Test
+    public void testDvbtFrontendSettingsWithLongFrequency() throws Exception {
+        DvbtFrontendSettings settings =
+                DvbtFrontendSettings
+                        .builder()
+                        .setFrequencyLong(6)
+                        .setTransmissionMode(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K)
+                        .setBandwidth(DvbtFrontendSettings.BANDWIDTH_1_7MHZ)
+                        .setConstellation(DvbtFrontendSettings.CONSTELLATION_16QAM_R)
+                        .setHierarchy(DvbtFrontendSettings.HIERARCHY_4_NATIVE)
+                        .setHighPriorityCodeRate(DvbtFrontendSettings.CODERATE_6_7)
+                        .setLowPriorityCodeRate(DvbtFrontendSettings.CODERATE_2_3)
+                        .setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_19_128)
+                        .setHighPriority(true)
+                        .setStandard(DvbtFrontendSettings.STANDARD_T2)
+                        .setMiso(false)
+                        .setPlpMode(DvbtFrontendSettings.PLP_MODE_MANUAL)
+                        .setPlpId(333)
+                        .setPlpGroupId(777)
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_DVBT, settings.getType());
+        assertEquals(6, settings.getFrequencyLong());
+        assertEquals(DvbtFrontendSettings.BANDWIDTH_1_7MHZ, settings.getBandwidth());
+        assertEquals(DvbtFrontendSettings.HIERARCHY_4_NATIVE, settings.getHierarchy());
+        assertEquals(DvbtFrontendSettings.CODERATE_6_7, settings.getHighPriorityCodeRate());
+        assertEquals(DvbtFrontendSettings.CODERATE_2_3, settings.getLowPriorityCodeRate());
+        assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_19_128, settings.getGuardInterval());
+        assertEquals(true, settings.isHighPriority());
+        assertEquals(DvbtFrontendSettings.STANDARD_T2, settings.getStandard());
+        assertEquals(false, settings.isMiso());
+        assertEquals(DvbtFrontendSettings.PLP_MODE_MANUAL, settings.getPlpMode());
+        assertEquals(333, settings.getPlpId());
+        assertEquals(777, settings.getPlpGroupId());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_EXTENDED_32K,
+                    settings.getTransmissionMode());
+            assertEquals(DvbtFrontendSettings.CONSTELLATION_16QAM_R, settings.getConstellation());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(DvbtFrontendSettings.TRANSMISSION_MODE_UNDEFINED,
+                    settings.getTransmissionMode());
+            assertEquals(DvbtFrontendSettings.CONSTELLATION_UNDEFINED, settings.getConstellation());
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testIsdbs3FrontendSettingsWithLongFrequency() throws Exception {
+        Isdbs3FrontendSettings settings =
+                Isdbs3FrontendSettings
+                        .builder()
+                        .setFrequencyLong(7)
+                        .setStreamId(2)
+                        .setStreamIdType(IsdbsFrontendSettings.STREAM_ID_TYPE_ID)
+                        .setModulation(Isdbs3FrontendSettings.MODULATION_MOD_BPSK)
+                        .setCodeRate(Isdbs3FrontendSettings.CODERATE_1_3)
+                        .setSymbolRate(555)
+                        .setRolloff(Isdbs3FrontendSettings.ROLLOFF_0_03)
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_ISDBS3, settings.getType());
+        assertEquals(7, settings.getFrequencyLong());
+        assertEquals(2, settings.getStreamId());
+        assertEquals(IsdbsFrontendSettings.STREAM_ID_TYPE_ID, settings.getStreamIdType());
+        assertEquals(Isdbs3FrontendSettings.MODULATION_MOD_BPSK, settings.getModulation());
+        assertEquals(Isdbs3FrontendSettings.CODERATE_1_3, settings.getCodeRate());
+        assertEquals(555, settings.getSymbolRate());
+        assertEquals(Isdbs3FrontendSettings.ROLLOFF_0_03, settings.getRolloff());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testIsdbsFrontendSettingsWithLongFrequency() throws Exception {
+        IsdbsFrontendSettings settings =
+                IsdbsFrontendSettings
+                        .builder()
+                        .setFrequencyLong(8)
+                        .setStreamId(3)
+                        .setStreamIdType(IsdbsFrontendSettings.STREAM_ID_TYPE_RELATIVE_NUMBER)
+                        .setModulation(IsdbsFrontendSettings.MODULATION_AUTO)
+                        .setCodeRate(IsdbsFrontendSettings.CODERATE_3_4)
+                        .setSymbolRate(667)
+                        .setRolloff(IsdbsFrontendSettings.ROLLOFF_0_35)
+                        .build();
+
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_ISDBS, settings.getType());
+        assertEquals(8, settings.getFrequencyLong());
+        assertEquals(3, settings.getStreamId());
+        assertEquals(
+                IsdbsFrontendSettings.STREAM_ID_TYPE_RELATIVE_NUMBER, settings.getStreamIdType());
+        assertEquals(IsdbsFrontendSettings.MODULATION_AUTO, settings.getModulation());
+        assertEquals(IsdbsFrontendSettings.CODERATE_3_4, settings.getCodeRate());
+        assertEquals(667, settings.getSymbolRate());
+        assertEquals(IsdbsFrontendSettings.ROLLOFF_0_35, settings.getRolloff());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+    }
+
+    @Test
+    public void testIsdbtFrontendSettingsWithLongFrequency() throws Exception {
+        IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+        builder.setFrequencyLong(9);
+        builder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_64QAM);
+        builder.setBandwidth(IsdbtFrontendSettings.BANDWIDTH_8MHZ);
+        builder.setMode(IsdbtFrontendSettings.MODE_2);
+        builder.setCodeRate(DvbtFrontendSettings.CODERATE_7_8);
+        builder.setGuardInterval(DvbtFrontendSettings.GUARD_INTERVAL_1_4);
+        builder.setServiceAreaId(10);
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+                    IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+            layerBuilder.setTimeInterleaveMode(IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            layerBuilder.setModulation(IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            layerBuilder.setCodeRate(DvbtFrontendSettings.CODERATE_5_6);
+            layerBuilder.setNumberOfSegments(0xFF);
+            IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+            builder.setLayerSettings(new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer, layer});
+            builder.setPartialReceptionFlag(IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+        }
+
+        IsdbtFrontendSettings settings = builder.build();
+        settings.setSpectralInversion(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL);
+        settings.setEndFrequencyLong(100);
+
+        assertEquals(FrontendSettings.TYPE_ISDBT, settings.getType());
+        assertEquals(9, settings.getFrequencyLong());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(IsdbtFrontendSettings.MODULATION_UNDEFINED, settings.getModulation());
+        } else {
+            assertEquals(IsdbtFrontendSettings.MODULATION_MOD_64QAM, settings.getModulation());
+        }
+        assertEquals(IsdbtFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
+        assertEquals(IsdbtFrontendSettings.MODE_2, settings.getMode());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(DvbtFrontendSettings.CODERATE_UNDEFINED, settings.getCodeRate());
+        } else {
+            assertEquals(DvbtFrontendSettings.CODERATE_7_8, settings.getCodeRate());
+        }
+        assertEquals(DvbtFrontendSettings.GUARD_INTERVAL_1_4, settings.getGuardInterval());
+        assertEquals(10, settings.getServiceAreaId());
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_NORMAL,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(100, settings.getEndFrequencyLong());
+        } else {
+            assertEquals(FrontendSettings.FRONTEND_SPECTRAL_INVERSION_UNDEFINED,
+                    settings.getFrontendSpectralInversion());
+            assertEquals(Tuner.INVALID_FRONTEND_SETTING_FREQUENCY, settings.getEndFrequencyLong());
+        }
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            IsdbtFrontendSettings.IsdbtLayerSettings[] layers = settings.getLayerSettings();
+            assertEquals(layers.length, 2);
+            assertEquals(layers[0].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            assertEquals(layers[0].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+            assertEquals(layers[0].getTimeInterleaveMode(),
+                    IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            assertEquals(layers[0].getNumberOfSegments(), 0xFF);
+            assertEquals(layers[1].getModulation(), IsdbtFrontendSettings.MODULATION_MOD_16QAM);
+            assertEquals(layers[1].getCodeRate(), DvbtFrontendSettings.CODERATE_5_6);
+            assertEquals(layers[1].getTimeInterleaveMode(),
+                    IsdbtFrontendSettings.TIME_INTERLEAVE_MODE_AUTO);
+            assertEquals(layers[1].getNumberOfSegments(), 0xFF);
+            assertEquals(settings.getPartialReceptionFlag(),
+                    IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+        }
+    }
+
+    @Test
+    public void testDtmbFrontendSettingsWithLongFrequency() throws Exception {
+        if (!TunerVersionChecker.checkHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1,
+                TAG + ": testDtmbFrontendSettings")) {
+            return;
+        }
+        DtmbFrontendSettings settings =
+                DtmbFrontendSettings
+                        .builder()
+                        .setFrequencyLong(6)
+                        .setModulation(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM)
+                        .setCodeRate(DtmbFrontendSettings.CODERATE_2_5)
+                        .setTransmissionMode(DtmbFrontendSettings.TRANSMISSION_MODE_C1)
+                        .setBandwidth(DtmbFrontendSettings.BANDWIDTH_8MHZ)
+                        .setTimeInterleaveMode(
+                                DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240)
+                        .setGuardInterval(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS)
+                        .build();
+        assertEquals(FrontendSettings.TYPE_DTMB, settings.getType());
+        assertEquals(6, settings.getFrequencyLong());
+        assertEquals(DtmbFrontendSettings.TRANSMISSION_MODE_C1, settings.getTransmissionMode());
+        assertEquals(DtmbFrontendSettings.BANDWIDTH_8MHZ, settings.getBandwidth());
+        assertEquals(DtmbFrontendSettings.MODULATION_CONSTELLATION_4QAM, settings.getModulation());
+        assertEquals(DtmbFrontendSettings.TIME_INTERLEAVE_MODE_TIMER_INT_240,
+                settings.getTimeInterleaveMode());
+        assertEquals(DtmbFrontendSettings.CODERATE_2_5, settings.getCodeRate());
+        assertEquals(DtmbFrontendSettings.GUARD_INTERVAL_PN_945_VARIOUS,
+                settings.getGuardInterval());
+    }
+
+    @Test
+    public void testFrontendInfoWithLongFrequency() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+        Map<Integer, FrontendInfo> infoMap = new HashMap<>();
+        for (FrontendInfo info : infos) {
+            infoMap.put(info.getId(), info);
+        }
+        for (int id : ids) {
+            FrontendInfo info = mTuner.getFrontendInfoById(id);
+            FrontendInfo infoFromMap = infoMap.get(id);
+            assertNotNull(info);
+            assertThat(info).isEqualTo(infoFromMap);
+            assertEquals(id, info.getId());
+            assertTrue(info.getFrequencyRangeLong().getLower() > 0);
+            assertTrue(info.getSymbolRateRange().getLower() >= 0);
+            assertTrue(info.getAcquireRangeLong() > 0);
+            info.getExclusiveGroupId();
+            info.getStatusCapabilities();
+
+            FrontendCapabilities caps = info.getFrontendCapabilities();
+            if (info.getType() <= FrontendSettings.TYPE_ISDBT) {
+                assertNotNull(caps);
+            }
+            switch(info.getType()) {
+                case FrontendSettings.TYPE_ANALOG:
+                    testAnalogFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_ATSC3:
+                    testAtsc3FrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_ATSC:
+                    testAtscFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_DVBC:
+                    testDvbcFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_DVBS:
+                    testDvbsFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_DVBT:
+                    testDvbtFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_ISDBS3:
+                    testIsdbs3FrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_ISDBS:
+                    testIsdbsFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_ISDBT:
+                    testIsdbtFrontendCapabilities(caps);
+                    break;
+                case FrontendSettings.TYPE_DTMB:
+                    testDtmbFrontendCapabilities(caps);
+                    break;
+                default:
+                    break;
+            }
+            infoMap.remove(id);
+        }
+        assertTrue(infoMap.isEmpty());
+    }
+
     private void testAnalogFrontendCapabilities(FrontendCapabilities caps) throws Exception {
         assertTrue(caps instanceof AnalogFrontendCapabilities);
         AnalogFrontendCapabilities analogCaps = (AnalogFrontendCapabilities) caps;
@@ -660,6 +1216,9 @@
         isdbtCaps.getModulationCapability();
         isdbtCaps.getCodeRateCapability();
         isdbtCaps.getGuardIntervalCapability();
+        isdbtCaps.getTimeInterleaveModeCapability();
+        isdbtCaps.isSegmentAutoSupported();
+        isdbtCaps.isFullSegmentSupported();
     }
 
     private void testDtmbFrontendCapabilities(FrontendCapabilities caps) throws Exception {
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java
new file mode 100644
index 0000000..e714508
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerResourceTestService.java
@@ -0,0 +1,108 @@
+/*
+ * 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.media.tv.tuner.cts;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.tv.tuner.Result;
+import android.media.tv.tuner.Tuner;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendSettings;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.util.List;
+
+public class TunerResourceTestService extends Service {
+    private static final String TAG = "TunerResourceTestService";
+    private Context mContext = null;
+    private Tuner mTuner = null;
+    private FrontendSettings mFeSettings;
+    private FrontendInfo mFeInfo;
+    private final Object mLock = new Object();
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        mContext = this;
+        return mBinder;
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        synchronized (mLock) {
+            closeTunerInternal();
+            return false;
+        }
+    }
+
+    private void closeTunerInternal() {
+        if (mTuner != null) {
+            mTuner.close();
+            mTuner = null;
+        }
+    }
+
+    private int tuneInternal(int frontendIndex) {
+        // make sure mTuner is not null
+        if (mTuner == null) {
+            Log.e(TAG, "tune called on null tuner");
+            return Result.INVALID_STATE;
+        }
+
+        // construct FrontendSettings to tune
+        List<FrontendInfo> infos = mTuner.getAvailableFrontendInfos();
+        mFeInfo = infos.get(frontendIndex);
+        mFeSettings = TunerTest.createFrontendSettings(mFeInfo);
+
+        // tune
+        return  mTuner.tune(mFeSettings);
+    }
+
+    private final ITunerResourceTestServer.Stub mBinder = new ITunerResourceTestServer.Stub() {
+        public void createTuner(int useCase) {
+            synchronized (mLock) {
+                closeTunerInternal();
+                mTuner = new Tuner(mContext, null, useCase);
+            }
+        }
+
+        public void tuneAsync(int frontendIndex) {
+            synchronized (mLock) {
+                tuneInternal(frontendIndex);
+            }
+        }
+
+        public int tune(int frontendIndex) {
+            synchronized (mLock) {
+                return tuneInternal(frontendIndex);
+            }
+        }
+
+        public void closeTuner() {
+            synchronized (mLock) {
+                closeTunerInternal();
+            }
+        }
+
+        public boolean verifyTunerIsNull() {
+            synchronized (mLock) {
+                return mTuner == null;
+            }
+        }
+    };
+}
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 62fa719..998a1a3 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
@@ -20,9 +20,14 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
+import android.content.ComponentName;
 import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.media.tv.tuner.DemuxCapabilities;
 import android.media.tv.tuner.Descrambler;
@@ -58,6 +63,8 @@
 import android.media.tv.tuner.filter.SectionSettingsWithSectionBits;
 import android.media.tv.tuner.filter.SectionSettingsWithTableInfo;
 import android.media.tv.tuner.filter.Settings;
+import android.media.tv.tuner.filter.SharedFilter;
+import android.media.tv.tuner.filter.SharedFilterCallback;
 import android.media.tv.tuner.filter.TemiEvent;
 import android.media.tv.tuner.filter.TimeFilter;
 import android.media.tv.tuner.filter.TlvFilterConfiguration;
@@ -91,6 +98,14 @@
 import android.media.tv.tuner.frontend.IsdbtFrontendSettings;
 import android.media.tv.tuner.frontend.OnTuneEventListener;
 import android.media.tv.tuner.frontend.ScanCallback;
+import android.media.tv.tunerresourcemanager.TunerFrontendInfo;
+import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
+import android.media.tv.tunerresourcemanager.TunerResourceManager;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
@@ -100,15 +115,19 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.time.Duration;
+import java.time.Instant;
 import java.util.List;
+import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -119,11 +138,130 @@
     public RequiredFeatureRule featureRule = new RequiredFeatureRule(
             PackageManager.FEATURE_TUNER);
 
-    private static final int TIMEOUT_MS = 10000;
+    private static final int TIMEOUT_MS = 10 * 1000;  // 10 seconds
+    private static final int SCAN_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
+    private static final long TIMEOUT_BINDER_SERVICE_SEC = 2;
 
     private Context mContext;
     private Tuner mTuner;
     private CountDownLatch mLockLatch = new CountDownLatch(1);
+    private TunerResourceManager mTunerResourceManager = null;
+    private TestServiceConnection mConnection;
+    private ISharedFilterTestServer mSharedFilterTestServer;
+
+    private class TestServiceConnection implements ServiceConnection {
+        private BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            mBlockingQueue.offer(service);
+        }
+
+        public void onServiceDisconnected(ComponentName componentName) {}
+
+        public IBinder getService() throws Exception {
+            final IBinder service =
+                    mBlockingQueue.poll(TIMEOUT_BINDER_SERVICE_SEC, TimeUnit.SECONDS);
+            return service;
+        }
+    }
+
+    private class TunerResourceTestServiceConnection implements ServiceConnection {
+        private BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+        @Override
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            mBlockingQueue.offer(service);
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName componentName){}
+
+        public ITunerResourceTestServer getService() throws Exception {
+            final IBinder service =
+                    mBlockingQueue.poll(TIMEOUT_BINDER_SERVICE_SEC, TimeUnit.SECONDS);
+            return ITunerResourceTestServer.Stub.asInterface(service);
+        }
+    }
+
+    private class TunerTestOnTuneEventListener implements OnTuneEventListener {
+        public static final int INVALID_TUNE_EVENT = -1;
+        private static final int SLEEP_TIME_MS = 100;
+        private static final int TIMEOUT_MS = 500;
+        private final ReentrantLock mLock = new ReentrantLock();
+        private final ConditionVariable mCV = new ConditionVariable();
+        private int mLastTuneEvent = INVALID_TUNE_EVENT;
+
+        @Override
+        public void onTuneEvent(int tuneEvent) {
+            synchronized (mLock) {
+                mLastTuneEvent = tuneEvent;
+                mCV.open();
+            }
+        }
+
+        public void resetLastTuneEvent() {
+            synchronized (mLock) {
+                mLastTuneEvent = INVALID_TUNE_EVENT;
+            }
+        }
+
+        public int getLastTuneEvent() {
+            try {
+                // yield to let the callback handling execute
+                Thread.sleep(SLEEP_TIME_MS);
+            } catch (Exception e) {
+                // ignore exception
+            }
+            synchronized (mLock) {
+                mCV.block(TIMEOUT_MS);
+                mCV.close();
+                return mLastTuneEvent;
+            }
+        }
+    }
+
+    private class TunerTestLnbCallback implements LnbCallback {
+        public static final int INVALID_LNB_EVENT = -1;
+        private static final int SLEEP_TIME_MS = 100;
+        private static final int TIMEOUT_MS = 500;
+        private final ReentrantLock mDMLock = new ReentrantLock();
+        private final ConditionVariable mDMCV = new ConditionVariable();
+        private boolean mOnDiseqcMessageCalled = false;
+
+        // will not test this as there is no good way to trigger this
+        @Override
+        public void onEvent(int lnbEventType) {}
+
+        // will test this instead
+        @Override
+        public void onDiseqcMessage(byte[] diseqcMessage) {
+            synchronized (mDMLock) {
+                mOnDiseqcMessageCalled = true;
+                mDMCV.open();
+            }
+        }
+
+        public void resetOnDiseqcMessageCalled() {
+            synchronized (mDMLock) {
+                mOnDiseqcMessageCalled = false;
+            }
+        }
+
+        public boolean getOnDiseqcMessageCalled() {
+            try {
+                // yield to let the callback handling execute
+                Thread.sleep(SLEEP_TIME_MS);
+            } catch (Exception e) {
+                // ignore exception
+            }
+
+            synchronized (mDMLock) {
+                mDMCV.block(TIMEOUT_MS);
+                mDMCV.close();
+                return mOnDiseqcMessageCalled;
+            }
+        }
+    }
 
     @Before
     public void setUp() throws Exception {
@@ -131,6 +269,12 @@
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
         mTuner = new Tuner(mContext, null, 100);
+
+        mConnection = new TestServiceConnection();
+        mContext.bindService(new Intent(mContext, SharedFilterTestService.class), mConnection,
+                Context.BIND_AUTO_CREATE);
+        mSharedFilterTestServer =
+                ISharedFilterTestServer.Stub.asInterface(mConnection.getService());
     }
 
     @After
@@ -139,6 +283,7 @@
           mTuner.close();
           mTuner = null;
         }
+        mContext.unbindService(mConnection);
     }
 
     @Test
@@ -151,25 +296,87 @@
         assertNotNull(mTuner);
         int version = TunerVersionChecker.getTunerVersion();
         assertTrue(version >= TunerVersionChecker.TUNER_VERSION_1_0);
-        assertTrue(version <= TunerVersionChecker.TUNER_VERSION_1_1);
+        assertTrue(version <= TunerVersionChecker.TUNER_VERSION_2_0);
     }
 
     @Test
-    public void testTuning() throws Exception {
+    public void testFrontendHardwareInfo() throws Exception {
+        String hwInfo = null;
+        try {
+            hwInfo = mTuner.getCurrentFrontendHardwareInfo();
+            if (TunerVersionChecker.isHigherOrEqualVersionTo(
+                    TunerVersionChecker.TUNER_VERSION_2_0)) {
+                fail("Get Frontend hardware info should throw IllegalStateException.");
+            } else {
+                assertNull(hwInfo);
+            }
+        } catch (IllegalStateException e) {
+            // pass
+        }
+
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
 
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
         int res = mTuner.tune(createFrontendSettings(info));
-        assertEquals(Tuner.RESULT_SUCCESS, res);
-        assertEquals(Tuner.RESULT_SUCCESS, mTuner.setLnaEnabled(false));
+        hwInfo = mTuner.getCurrentFrontendHardwareInfo();
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertNotNull(hwInfo);
+            assertFalse(hwInfo.isEmpty());
+        } else {
+            assertNull(hwInfo);
+        }
         res = mTuner.cancelTuning();
         assertEquals(Tuner.RESULT_SUCCESS, res);
     }
 
     @Test
+    public void testTuning() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        res = mTuner.setLnaEnabled(false);
+        assertTrue((res == Tuner.RESULT_SUCCESS) || (res == Tuner.RESULT_UNAVAILABLE));
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+    }
+
+    @Test
+    public void testMultiTuning() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
+        assertFalse(ids.isEmpty());
+
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        int res = mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // Tune again with the same frontend.
+        mTuner.tune(createFrontendSettings(info));
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        for (int i = 1; i < ids.size(); i++) {
+            FrontendInfo info2 = mTuner.getFrontendInfoById(ids.get(i));
+            if (info2.getType() != info.getType()) {
+                res = mTuner.tune(createFrontendSettings(info2));
+                assertEquals(Tuner.RESULT_INVALID_STATE, res);
+            }
+        }
+    }
+
+    @Test
     public void testScanning() throws Exception {
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
         for (int id : ids) {
             FrontendInfo info = mTuner.getFrontendInfoById(id);
@@ -181,7 +388,7 @@
                         getExecutor(),
                         getScanCallback());
                assertEquals(Tuner.RESULT_SUCCESS, res);
-               assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+               assertTrue(mLockLatch.await(SCAN_TIMEOUT_MS, TimeUnit.MILLISECONDS));
                res = mTuner.cancelScanning();
                assertEquals(Tuner.RESULT_SUCCESS, res);
             }
@@ -192,18 +399,17 @@
     @Test
     public void testFrontendStatus() throws Exception {
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
 
         for (int id : ids) {
-            if (mTuner == null) {
-                mTuner = new Tuner(mContext, null, 100);
-            }
-            FrontendInfo info = mTuner.getFrontendInfoById(id);
-            int res = mTuner.tune(createFrontendSettings(info));
+            Tuner tuner = new Tuner(mContext, null, 100);
+            FrontendInfo info = tuner.getFrontendInfoById(id);
+            int res = tuner.tune(createFrontendSettings(info));
 
             int[] statusCapabilities = info.getStatusCapabilities();
             assertNotNull(statusCapabilities);
-            FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+            FrontendStatus status = tuner.getFrontendStatus(statusCapabilities);
             assertNotNull(status);
 
             for (int i = 0; i < statusCapabilities.length; i++) {
@@ -263,6 +469,7 @@
                         status.getMer();
                         break;
                     case FrontendStatus.FRONTEND_STATUS_TYPE_FREQ_OFFSET:
+                        status.getFreqOffsetLong();
                         status.getFreqOffset();
                         break;
                     case FrontendStatus.FRONTEND_STATUS_TYPE_HIERARCHY:
@@ -326,10 +533,22 @@
                     case FrontendStatus.FRONTEND_STATUS_TYPE_IS_SHORT_FRAMES_ENABLED:
                         status.isShortFramesEnabled();
                         break;
+                    case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_MODE:
+                        status.getIsdbtMode();
+                        break;
+                    case FrontendStatus.FRONTEND_STATUS_TYPE_ISDBT_PARTIAL_RECEPTION_FLAG:
+                        status.getIsdbtPartialReceptionFlag();
+                        break;
+                    case FrontendStatus.FRONTEND_STATUS_TYPE_STREAM_IDS:
+                        status.getStreamIds();
+                        break;
+                    case FrontendStatus.FRONTEND_STATUS_TYPE_DVBT_CELL_IDS:
+                        status.getDvbtCellIds();
+                        break;
                 }
             }
-            mTuner.close();
-            mTuner = null;
+            tuner.close();
+            tuner = null;
         }
     }
 
@@ -346,6 +565,57 @@
     }
 
     @Test
+    public void testLnbAddAndRemoveCallback() throws Exception {
+        TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+        Lnb lnb = mTuner.openLnb(getExecutor(), lnbCB1);
+        if (lnb == null) {
+            return;
+        }
+
+        assertEquals(lnb.setVoltage(Lnb.VOLTAGE_5V), Tuner.RESULT_SUCCESS);
+        assertEquals(lnb.setTone(Lnb.TONE_NONE), Tuner.RESULT_SUCCESS);
+        assertEquals(
+                lnb.setSatellitePosition(Lnb.POSITION_A), Tuner.RESULT_SUCCESS);
+        lnb.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+        int res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // create sharee
+        Tuner sharee = new Tuner(mContext, null, 100);
+        sharee.shareFrontendFromTuner(mTuner);
+        TunerTestLnbCallback lnbCB2 = new TunerTestLnbCallback();
+
+        // add it as sharee
+        lnb.addCallback(lnbCB2, getExecutor());
+
+        // check callback
+        lnb.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+        assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+        lnbCB2.resetOnDiseqcMessageCalled();
+
+        // remove sharee the sharee (should succeed)
+        assertTrue(lnb.removeCallback(lnbCB2));
+
+        // check callback (only the original owner gets callback
+        lnb.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+        assertFalse(lnbCB2.getOnDiseqcMessageCalled());
+        lnbCB2.resetOnDiseqcMessageCalled();
+
+        sharee.close();
+    }
+
+    @Test
     public void testOpenLnbByname() throws Exception {
         Lnb lnb = mTuner.openLnbByName("default", getExecutor(), getLnbCallback());
         if (lnb != null) {
@@ -367,6 +637,7 @@
     public void testFrontendToCiCam() throws Exception {
         // tune to get frontend resource
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
         int res = mTuner.tune(createFrontendSettings(info));
@@ -393,9 +664,12 @@
     // open filter to get demux resource
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_AUDIO, 1000, getExecutor(), getFilterCallback());
+        assertNotNull(f);
+        assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
         Settings settings = AvSettings
                 .builder(Filter.TYPE_TS, true)
                 .setPassthrough(false)
+                .setUseSecureMemory(false)
                 .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                 .build();
         FilterConfiguration config = TsFilterConfiguration
@@ -441,6 +715,7 @@
 
         // Tune a frontend before start the filter
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
 
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -467,6 +742,7 @@
         Settings settings = AvSettings
                 .builder(Filter.TYPE_TS, true)
                 .setPassthrough(false)
+                .setUseSecureMemory(false)
                 .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                 .build();
         FilterConfiguration config = TsFilterConfiguration
@@ -478,6 +754,7 @@
 
         // Tune a frontend before start the filter
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
 
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -509,7 +786,7 @@
     public void testIpFilter() throws Exception {
         Filter f = mTuner.openFilter(
                 Filter.TYPE_IP, Filter.SUBTYPE_IP, 1000, getExecutor(), getFilterCallback());
-        assertNotNull(f);
+        if (f == null) return;
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
 
         FilterConfiguration config = IpFilterConfiguration
@@ -526,6 +803,7 @@
 
         // Tune a frontend before start the filter
         List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
 
         FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
@@ -544,7 +822,7 @@
     public void testAlpSectionFilterConfig() throws Exception {
         Filter f = mTuner.openFilter(
                 Filter.TYPE_ALP, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
-        assertNotNull(f);
+        if (f == null) return;
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
 
         SectionSettingsWithSectionBits settings =
@@ -574,7 +852,7 @@
     public void testMmtpPesFilterConfig() throws Exception {
         Filter f = mTuner.openFilter(
                 Filter.TYPE_MMTP, Filter.SUBTYPE_PES, 1000, getExecutor(), getFilterCallback());
-        assertNotNull(f);
+        if (f == null) return;
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
 
         PesSettings settings =
@@ -600,14 +878,22 @@
         Filter f = mTuner.openFilter(
                 Filter.TYPE_MMTP, Filter.SUBTYPE_DOWNLOAD,
                 1000, getExecutor(), getFilterCallback());
-        assertNotNull(f);
+        if (f == null) return;
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
 
-        DownloadSettings settings =
-                DownloadSettings
-                        .builder(Filter.TYPE_MMTP)
-                        .setDownloadId(2)
-                        .build();
+        DownloadSettings.Builder builder = DownloadSettings.builder(Filter.TYPE_MMTP);
+        if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            builder.setUseDownloadId(true);
+        }
+        builder.setDownloadId(2);
+        DownloadSettings settings = builder.build();
+        if (!TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            assertEquals(settings.useDownloadId(), true);
+        } else {
+            assertEquals(settings.useDownloadId(), false);
+        }
+        assertEquals(settings.getDownloadId(), 2);
+
         MmtpFilterConfiguration config =
                 MmtpFilterConfiguration
                         .builder()
@@ -631,6 +917,7 @@
                 AvSettings
                         .builder(Filter.TYPE_TS, true) // is Audio
                         .setPassthrough(false)
+                        .setUseSecureMemory(false)
                         .setAudioStreamType(AvSettings.AUDIO_STREAM_TYPE_MPEG1)
                         .build();
         TsFilterConfiguration config =
@@ -677,7 +964,7 @@
     public void testTlvTlvFilterConfig() throws Exception {
         Filter f = mTuner.openFilter(
                 Filter.TYPE_TLV, Filter.SUBTYPE_TLV, 1000, getExecutor(), getFilterCallback());
-        assertNotNull(f);
+        if (f == null) return;
         assertNotEquals(Tuner.INVALID_FILTER_ID, f.getId());
 
         TlvFilterConfiguration config =
@@ -778,18 +1065,1255 @@
     }
 
     @Test
+    public void testResourceReclaimed() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // first tune with mTuner to acquire resource
+        int res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        // now tune with a higher priority tuner to have mTuner's resource reclaimed
+        Tuner higherPrioTuner = new Tuner(mContext, null, 200);
+        res = higherPrioTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(higherPrioTuner.getFrontendInfo());
+
+        higherPrioTuner.close();
+    }
+
+    // TODO: change this to use ITunerResourceTestServer
+    @Test
+    public void testResourceReclaimedDifferentThread() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // first tune with mTuner to acquire resource
+        int res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        // now tune with a higher priority tuner to have mTuner's resource reclaimed
+        TunerHandler tunerHandler = createTunerHandler(null);
+        Message msgCreate = new Message();
+        msgCreate.what = MSG_TUNER_HANDLER_CREATE;
+        msgCreate.arg1 = 200;
+        tunerHandler.sendMessage(msgCreate);
+        mTunerHandlerTaskComplete.block();
+        mTunerHandlerTaskComplete.close();
+
+        Message msgTune = new Message();
+        msgTune.what = MSG_TUNER_HANDLER_TUNE;
+        msgTune.obj = (Object) feSettings;
+        tunerHandler.sendMessage(msgTune);
+
+        // call mTuner.close in parallel
+        int sleepMS = 1;
+        //int sleepMS = (int) (Math.random() * 3.);
+        try {
+            Thread.sleep(sleepMS);
+        } catch (Exception e) { } // ignore
+        mTuner.close();
+        mTuner = null;
+
+        mTunerHandlerTaskComplete.block();
+        mTunerHandlerTaskComplete.close();
+        res = tunerHandler.getResult();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        Tuner higherPrioTuner = tunerHandler.getTuner();
+        assertNotNull(higherPrioTuner.getFrontendInfo());
+
+        Message msgClose = new Message();
+        msgClose.what = MSG_TUNER_HANDLER_CLOSE;
+        tunerHandler.sendMessage(msgClose);
+
+    }
+
+    @Test
+    public void testResourceReclaimedDifferentProcess() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        int frontendIndex = 0;
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(frontendIndex));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // set up the test server
+        TunerResourceTestServiceConnection connection = new TunerResourceTestServiceConnection();
+        ITunerResourceTestServer tunerResourceTestServer = null;
+        Intent intent = new Intent(mContext, TunerResourceTestService.class);
+
+        // get the TunerResourceTestService
+        mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        tunerResourceTestServer = connection.getService();
+
+        // CASE1 - normal reclaim
+        //
+        // first tune with mTuner to acquire resource
+        int res = mTuner.tune(feSettings);
+        boolean tunerReclaimed = false;
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        // now tune with a higher priority tuner to have mTuner's resource reclaimed
+
+        // create higher priority tuner
+        tunerResourceTestServer.createTuner(200);
+
+        // now tune on higher priority tuner to get mTuner reclaimed
+        res = tunerResourceTestServer.tune(frontendIndex);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        try {
+            int[] statusCapabilities = info.getStatusCapabilities();
+            mTuner.getFrontendStatus(statusCapabilities);
+
+        } catch (IllegalStateException e) {
+            tunerReclaimed = true;
+            mTuner.close();
+            mTuner = null;
+        }
+
+        // confirm if the mTuner is reclaimed
+        assertTrue(tunerReclaimed);
+
+        tunerResourceTestServer.closeTuner();
+        assertTrue(tunerResourceTestServer.verifyTunerIsNull());
+
+
+        // CASE2 - race between Tuner#close() and reclaim
+        mTuner = new Tuner(mContext, null, 100);
+        res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        tunerResourceTestServer.createTuner(200);
+        tunerResourceTestServer.tuneAsync(frontendIndex);
+
+        // adjust timing to induce race/deadlock
+        int sleepMS = 4;
+        //int sleepMS = (int) (Math.random() * 5.);
+        try {
+            Thread.sleep(sleepMS);
+        } catch (Exception e) { } // ignore
+        mTuner.close();
+        mTuner = null;
+
+        tunerResourceTestServer.closeTuner();
+
+        // unbind
+        mContext.unbindService(connection);
+    }
+
+    @Test
     public void testShareFrontendFromTuner() throws Exception {
+        Tuner tuner100 = new Tuner(mContext, null, 100);
+        List<Integer> ids = tuner100.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = tuner100.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+        int[] statusTypes = {1};
+        boolean exceptionThrown = false;
+        int res;
+
+        // CASE1: check resource reclaim while sharee's priority < owner's priority
+        // let tuner100 share from tuner200
+        Tuner tuner200 = new Tuner(mContext, null, 200);
+        res = tuner200.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        info = tuner200.getFrontendInfoById(ids.get(0));
+        res = tuner200.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        tuner100 = new Tuner(mContext, null, 100);
+        tuner100.shareFrontendFromTuner(tuner200);
+        // call openFilter to trigger ITunerDemux.setFrontendDataSourceById()
+        Filter f = tuner100.openFilter(
+                Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
+        assertNotNull(f);
+
+        // setup onTuneCallback
+        TunerTestOnTuneEventListener cb100 = new TunerTestOnTuneEventListener();
+        TunerTestOnTuneEventListener cb200 = new TunerTestOnTuneEventListener();
+
+        // tune again on the owner
+        info = tuner200.getFrontendInfoById(ids.get(1));
+        tuner100.setOnTuneEventListener(getExecutor(), cb100);
+        tuner200.setOnTuneEventListener(getExecutor(), cb200);
+        res = tuner200.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertEquals(OnTuneEventListener.SIGNAL_LOCKED, cb100.getLastTuneEvent());
+        assertEquals(OnTuneEventListener.SIGNAL_LOCKED, cb200.getLastTuneEvent());
+        tuner100.clearOnTuneEventListener();
+        tuner200.clearOnTuneEventListener();
+
+        // now let the higher priority tuner steal the resource
+        Tuner tuner300 = new Tuner(mContext, null, 300);
+        res = tuner300.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // confirm owner & sharee's resource gets reclaimed by confirming an exception is thrown
+        exceptionThrown = false;
+        try {
+            tuner200.getFrontendStatus(statusTypes);
+        } catch (Exception e) {
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+
+        exceptionThrown = false;
+        try {
+            tuner100.getFrontendStatus(statusTypes);
+        } catch (Exception e) {
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+
+        tuner100.close();
+        tuner200.close();
+        tuner300.close();
+
+
+        // CASE2: check resource reclaim fail when sharee's priority > new requester
+        tuner100 = new Tuner(mContext, null, 100);
+        res = tuner100.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        tuner300 = new Tuner(mContext, null, 300);
+        tuner300.shareFrontendFromTuner(tuner100);
+        f = tuner100.openFilter(
+                Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
+        assertNotNull(f);
+
+        tuner200 = new Tuner(mContext, null, 200);
+        res = tuner200.tune(feSettings);
+        assertNotEquals(Tuner.RESULT_SUCCESS, res);
+
+        // confirm the original tuner is still intact
+        res = tuner100.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        tuner100.close();
+        tuner200.close();
+        tuner300.close();
+    }
+
+    private void testTransferFeOwnershipSingleTuner() {
+        List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) {
+            return;
+        }
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // SCENARIO 1 - transfer and close the previous owner
+
+        // First create a tuner and tune() to acquire frontend resource
+        Tuner tunerA = new Tuner(mContext, null, 100);
+        int res = tunerA.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // Create another tuner and share frontend from tunerA
+        Tuner tunerB = new Tuner(mContext, null, 500);
+        tunerB.shareFrontendFromTuner(tunerA);
+        DvrRecorder d = tunerB.openDvrRecorder(100, getExecutor(), getRecordListener());
+        assertNotNull(d);
+
+        // Call transferOwner in the wrong configurations and confirm it fails
+        assertEquals(Tuner.RESULT_INVALID_STATE, tunerB.transferOwner(tunerA));
+        Tuner nonSharee = new Tuner(mContext, null, 300);
+        assertEquals(Tuner.RESULT_INVALID_STATE, tunerA.transferOwner(nonSharee));
+        nonSharee.close();
+
+        // Now call it correctly to transfer ownership from tunerA to tunerB
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+        // Close the original owner (tunerA)
+        tunerA.close();
+
+        // Confirm the new owner (tunerB) is still functional
+        assertNotNull(tunerB.getFrontendInfo());
+
+        // Close the new owner (tunerB)
+        d.close();
+        tunerB.close();
+
+        // SCENARIO 2 - transfer and closeFrontend and tune on the previous owner
+
+        // First create a tuner and tune() to acquire frontend resource
+        tunerA = new Tuner(mContext, null, 200);
+        res = tunerA.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // Create another tuner and share frontend from tunerA
+        tunerB = new Tuner(mContext, null, 100);
+        tunerB.shareFrontendFromTuner(tunerA);
+        assertNotNull(tunerB.getFrontendInfo());
+
+        // Transfer ownership from tunerA to tunerB
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+        // Close frontend for the original owner (tunerA)
+        tunerA.closeFrontend();
+
+        // Confirm tune works without going through Tuner.close() even after transferOwner()
+        // The purpose isn't to get tunerB's frontend revoked, but doing so as singletuner
+        // based test has wider coverage
+        res = tunerA.tune(feSettings); // this should reclaim tunerB
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // Confirm tuberB is revoked
+        assertNull(tunerB.getFrontendInfo());
+
+        // Close tunerA
+        tunerA.close();
+
+        // close TunerB just in case
+        tunerB.close();
+    }
+
+    private void testTransferFeAndCiCamOwnership() {
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertNotNull(ids);
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // Create tuner and tune to get frontend resource
+        Tuner tunerA = new Tuner(mContext, null, 100);
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+
+        int ciCamId = 0;
+        boolean linkCiCamToFrontendSupported = false;
+
+        // connect CiCam to Frontend
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_1_1)) {
+            // TODO: get real CiCam id from MediaCas
+            assertEquals(Tuner.RESULT_SUCCESS, tunerA.connectFrontendToCiCam(ciCamId));
+            linkCiCamToFrontendSupported = true;
+        } else {
+            assertEquals(Tuner.INVALID_LTS_ID, tunerA.connectFrontendToCiCam(ciCamId));
+        }
+
+        // connect CiCam to Demux
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.connectCiCam(ciCamId));
+
+        // start another tuner and connect the same CiCam to its own demux
+        Tuner tunerB = new Tuner(mContext, null, 400);
+        tunerB.shareFrontendFromTuner(tunerA);
+        assertNotNull(tunerB.getFrontendInfo());
+        assertEquals(Tuner.RESULT_SUCCESS, tunerB.connectCiCam(ciCamId));
+
+        // unlink CiCam to Demux in tunerA and transfer ownership
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.disconnectCiCam());
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+
+        // close the original owner
+        tunerA.close();
+
+        // disconnect CiCam from demux
+        assertEquals(Tuner.RESULT_SUCCESS, tunerB.disconnectCiCam());
+
+        // let Tuner.close() handle the release of CiCam
+        tunerB.close();
+
+        // now that the CiCam is released, disconnectFrontendToCiCam() should fail
+        assertEquals(Tuner.RESULT_UNAVAILABLE, tunerB.disconnectFrontendToCiCam(ciCamId));
+
+        // see if tune still works just in case
+        tunerA = new Tuner(mContext, null, 100);
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+        tunerA.close();
+    }
+
+    private void testTransferFeAndLnbOwnership() {
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertNotNull(ids);
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+
+        // Create tuner and tune to acquire frontend resource
+        Tuner tunerA = new Tuner(mContext, null, 100);
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.tune(feSettings));
+
+        // Open Lnb and check the callback
+        TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+        Lnb lnbA = tunerA.openLnb(getExecutor(), lnbCB1);
+        assertNotNull(lnbA);
+        lnbA.setVoltage(Lnb.VOLTAGE_5V);
+        lnbA.setTone(Lnb.TONE_CONTINUOUS);
+        lnbA.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+
+        // Create another tuner and share from tunerB
+        Tuner tunerB = new Tuner(mContext, null, 300);
+        tunerB.shareFrontendFromTuner(tunerA);
+
+        // add sharee and check the callback
+        TunerTestLnbCallback lnbCB2 = new TunerTestLnbCallback();
+        lnbA.addCallback(lnbCB2, getExecutor());
+        lnbA.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+        assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+        lnbCB2.resetOnDiseqcMessageCalled();
+
+        // transfer owner and check callback
+        assertEquals(Tuner.RESULT_SUCCESS, tunerA.transferOwner(tunerB));
+        lnbA.sendDiseqcMessage(new byte[] {1, 2});
+        assertTrue(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+        assertTrue(lnbCB2.getOnDiseqcMessageCalled());
+        lnbCB2.resetOnDiseqcMessageCalled();
+
+        // remove the owner callback (just for testing)
+        assertTrue(lnbA.removeCallback(lnbCB2));
+
+        // remove sharee and check callback
+        assertTrue(lnbA.removeCallback(lnbCB1));
+        lnbA.sendDiseqcMessage(new byte[] {1, 2});
+        assertFalse(lnbCB1.getOnDiseqcMessageCalled());
+        lnbCB1.resetOnDiseqcMessageCalled();
+        assertFalse(lnbCB2.getOnDiseqcMessageCalled());
+        lnbCB2.resetOnDiseqcMessageCalled();
+
+        // close the original owner
+        tunerA.close();
+
+        // confirm the new owner is still intact
+        int[] statusCapabilities = info.getStatusCapabilities();
+        assertNotNull(statusCapabilities);
+        FrontendStatus status = tunerB.getFrontendStatus(statusCapabilities);
+        assertNotNull(status);
+
+        tunerB.close();
+    }
+
+    @Test
+    public void testTransferOwner() throws Exception {
+        testTransferFeOwnershipSingleTuner();
+        testTransferFeAndCiCamOwnership();
+        testTransferFeAndLnbOwnership();
+    }
+
+    @Test
+    public void testClose() throws Exception {
         Tuner other = new Tuner(mContext, null, 100);
+
         List<Integer> ids = other.getFrontendIds();
+        if (ids == null) return;
         assertFalse(ids.isEmpty());
         FrontendInfo info = other.getFrontendInfoById(ids.get(0));
 
-        // call tune() to open frontend resource
-        int res = other.tune(createFrontendSettings(info));
+        FrontendSettings feSettings = createFrontendSettings(info);
+        int res = other.tune(feSettings);
         assertEquals(Tuner.RESULT_SUCCESS, res);
         assertNotNull(other.getFrontendInfo());
-        mTuner.shareFrontendFromTuner(other);
+
         other.close();
+
+        // make sure pre-existing tuner is still functional
+        res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        // Frontend sharing scenario 1: close owner first
+        // create sharee
+        Tuner sharee = new Tuner(mContext, null, 100);
+        sharee.shareFrontendFromTuner(mTuner);
+
+        // close the owner
+        mTuner.close();
+        mTuner = null;
+
+        // check the sharee is also closed
+        // tune() would have failed even before close() but still..
+        // TODO: fix this once callback sharing is implemented
+        res = sharee.tune(feSettings);
+        assertEquals(Tuner.RESULT_UNAVAILABLE, res);
+
+        sharee.close();
+
+        // Frontend sharing scenario 2: close sharee first
+        // create owner first
+        mTuner = new Tuner(mContext, null, 100);
+        res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        // create sharee
+        sharee = new Tuner(mContext, null, 100);
+        sharee.shareFrontendFromTuner(mTuner);
+
+        // close sharee
+        sharee.close();
+
+        // confirm owner is still intact
+        int[] statusCapabilities = info.getStatusCapabilities();
+        assertNotNull(statusCapabilities);
+        FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+        assertNotNull(status);
+
+    }
+
+    @Test
+    public void testCloseFrontend() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        if (ids == null) {
+            return;
+        }
+
+        // SCENARIO 1 - without Lnb
+        assertFalse(ids.isEmpty());
+        FrontendInfo info = mTuner.getFrontendInfoById(ids.get(0));
+        FrontendSettings feSettings = createFrontendSettings(info);
+        int res = mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+
+        // now close frontend
+        mTuner.closeFrontend();
+
+        // confirm frontend is closed
+        int[] statusCapabilities = info.getStatusCapabilities();
+        boolean frontendClosed = false;
+        try {
+            mTuner.getFrontendStatus(statusCapabilities);
+
+        } catch (IllegalStateException e) {
+            frontendClosed = true;
+        }
+        assertTrue(frontendClosed);
+
+        // now tune to a different setting
+        info = mTuner.getFrontendInfoById(ids.get(1));
+        feSettings = createFrontendSettings(info);
+        mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+        FrontendStatus status = mTuner.getFrontendStatus(statusCapabilities);
+        assertNotNull(status);
+
+        // SCENARIO 2 - with Lnb
+
+        TunerTestLnbCallback lnbCB1 = new TunerTestLnbCallback();
+        Lnb lnb = mTuner.openLnb(getExecutor(), lnbCB1);
+        if (lnb == null) {
+            return;
+        }
+
+        mTuner.closeFrontend();
+        // confirm frontend is closed
+        statusCapabilities = info.getStatusCapabilities();
+        frontendClosed = false;
+        try {
+            mTuner.getFrontendStatus(statusCapabilities);
+
+        } catch (IllegalStateException e) {
+            frontendClosed = true;
+        }
+        assertTrue(frontendClosed);
+
+        info = mTuner.getFrontendInfoById(ids.get(0));
+        feSettings = createFrontendSettings(info);
+        mTuner.tune(feSettings);
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        assertNotNull(mTuner.getFrontendInfo());
+        status = mTuner.getFrontendStatus(statusCapabilities);
+        assertNotNull(status);
+    }
+
+    @Test
+    public void testHasUnusedFrontend1() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+            // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+            infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+            infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+            infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+            infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+            infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+            infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+            mTunerResourceManager.setFrontendInfoList(infos);
+
+            Tuner A = new Tuner(mContext, null, 100);
+            Tuner B = new Tuner(mContext, null, 100);
+            Tuner C = new Tuner(mContext, null, 100);
+
+            // check before anyone holds resource
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_UNDEFINED));
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_ATSC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            // let B hold resource
+            assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+                             true /* expectedResult */, 1 /* expectedHandle */);
+
+            // check when one of the two exclusive groups are held
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            assertTrue(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+            // let C hold the resource
+            assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+                             true /* expectedResult */, 5 /* expectedHandle */);
+
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            assertFalse(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertFalse(C.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+            // let go of B's resource
+            B.close();
+
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            assertTrue(B.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(C.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+
+            C.close();
+            A.close();
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    @Test
+    public void testHasUnusedFrontend2() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            TunerFrontendInfo[] infos = new TunerFrontendInfo[5];
+            // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+            infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+            infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+            infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBT, 2);
+            infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBC, 2);
+            infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBS, 3);
+
+            mTunerResourceManager.setFrontendInfoList(infos);
+
+            Tuner A = new Tuner(mContext, null, 100);
+            Tuner B = new Tuner(mContext, null, 100);
+            Tuner C = new Tuner(mContext, null, 100);
+
+            // let B hold resource
+            assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+                             true /* expectedResult */, 1 /* expectedHandle */);
+
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            // let C hold the resource
+            assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+                             true /* expectedResult */, 4 /* expectedHandle */);
+
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertFalse(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            B.close();
+            C.close();
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    @Test
+    public void testHasUnusedFrontend3() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+            // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+            infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+            infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+            infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+            infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+            infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+            infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+            mTunerResourceManager.setFrontendInfoList(infos);
+
+            Tuner A = new Tuner(mContext, null, 100);
+            Tuner B = new Tuner(mContext, null, 100);
+            Tuner C = new Tuner(mContext, null, 100);
+
+            // let B hold resource
+            assignFeResource(B.getClientId(), FrontendSettings.TYPE_DVBT,
+                             true /* expectedResult */, 1 /* expectedHandle */);
+
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            // let C share from B
+            mTunerResourceManager.shareFrontend(C.getClientId(), B.getClientId());
+
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBT));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBC));
+            assertTrue(A.hasUnusedFrontend(FrontendSettings.TYPE_DVBS));
+
+            A.close();
+            C.close();
+            B.close();
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    @Test
+    public void testIsLowestPriorityCornerCases() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            setupSingleTunerSetupForIsLowestPriority();
+
+            // must return true when non existing frontend type is specified
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_UNDEFINED));
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_ATSC));
+
+            // must return true when no one is holding the resource
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+
+            // must return true when the callee is the only one holding the resource
+            assignFeResource(mTuner.getClientId(), FrontendSettings.TYPE_DVBT,
+                             true /* expectedResult */, 1 /* expectedHandle */);
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(mTuner.isLowestPriority(FrontendSettings.TYPE_DVBT));
+
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    @Test
+    public void testIsLowestPriorityTwoClients() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            setupSingleTunerSetupForIsLowestPriority();
+            testTwoClientsForIsLowestPriority(200, 100); // A > B
+            testTwoClientsForIsLowestPriority(100, 200); // A < B
+            testTwoClientsForIsLowestPriority(100, 100); // A = B
+
+            setupDualTunerSetupForIsLowestPriority();
+            testTwoClientsForIsLowestPriority(200, 100); // A > B
+            testTwoClientsForIsLowestPriority(100, 200); // A < B
+            testTwoClientsForIsLowestPriority(100, 100); // A = B
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    @Test
+    public void testIsLowestPriorityThreeClients() throws Exception {
+        prepTRMCustomFeResourceMapTest();
+
+        // Use try block to ensure restoring the TunerResourceManager
+        // Note: the handles will be changed from the original value, but should be OK
+        try {
+            setupDualTunerSetupForIsLowestPriority();
+            testThreeClientsForIsLowestPriority(300, 200, 100); // A > B > C
+            testThreeClientsForIsLowestPriority(300, 100, 200); // A > C > B
+            testThreeClientsForIsLowestPriority(200, 300, 100); // B > A > C
+            testThreeClientsForIsLowestPriority(200, 100, 300); // C > A > B
+            testThreeClientsForIsLowestPriority(100, 300, 200); // B > C > A
+            testThreeClientsForIsLowestPriority(100, 200, 300); // C > B > A
+            testThreeClientsForIsLowestPriority(100, 100, 100); // A = B = C
+            testThreeClientsForIsLowestPriority(200, 200, 100); // A = B > C
+            testThreeClientsForIsLowestPriority(200, 100, 100); // A > B = C
+            testThreeClientsForIsLowestPriority(200, 100, 200); // A = C > B
+            testThreeClientsForIsLowestPriority(200, 300, 200); // B > A = C
+            testThreeClientsForIsLowestPriority(100, 100, 200); // C > A = B
+            testThreeClientsForIsLowestPriority(100, 200, 200); // B = C > A
+        } catch (Exception e) {
+            throw (e);
+        } finally {
+            cleanupTRMCustomFeResourceMapTest();
+        }
+    }
+
+    private TunerFrontendInfo tunerFrontendInfo(
+            int handle, int frontendType, int exclusiveGroupId) {
+        TunerFrontendInfo info = new TunerFrontendInfo();
+        info.handle = handle;
+        info.type = frontendType;
+        info.exclusiveGroupId = exclusiveGroupId;
+        return info;
+    }
+
+    /**
+     * Prep function for TunerTest that requires custom frontend resource map
+     */
+    private void prepTRMCustomFeResourceMapTest() {
+        if (mTunerResourceManager == null) {
+            mTunerResourceManager = (TunerResourceManager)
+                    mContext.getSystemService(Context.TV_TUNER_RESOURCE_MGR_SERVICE);
+        }
+        mTunerResourceManager.storeResourceMap(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+        mTunerResourceManager.clearResourceMap(TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+    }
+
+    /**
+     * Clean up function for TunerTest that requires custom frontend resource map
+     */
+    private void cleanupTRMCustomFeResourceMapTest() {
+        // first close mTuner in case a frontend resource is opened
+        if (mTuner != null) {
+            mTuner.close();
+            mTuner = null;
+        }
+
+        // now restore the original frontend resource map
+        if (mTunerResourceManager != null) {
+            mTunerResourceManager.restoreResourceMap(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+        }
+    }
+
+    private void clearFrontendInfoList() {
+        if (mTunerResourceManager != null) {
+            mTunerResourceManager.clearResourceMap(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_FRONTEND);
+        }
+    }
+
+    private void assignFeResource(int clientId, int frontendType,
+                                  boolean expectedResult, int expectedHandle) {
+        int[] feHandle = new int[1];
+        TunerFrontendRequest request = new TunerFrontendRequest();
+        request.clientId = clientId;
+        request.frontendType = frontendType;
+        boolean granted = mTunerResourceManager.requestFrontend(request, feHandle);
+        assertEquals(granted, expectedResult);
+        assertEquals(feHandle[0], expectedHandle);
+    }
+
+    private void setupSingleTunerSetupForIsLowestPriority() {
+        // first clear the frontend resource to register new set of resources
+        clearFrontendInfoList();
+
+        TunerFrontendInfo[] infos = new TunerFrontendInfo[3];
+        // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+        infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+        infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+        infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+
+        mTunerResourceManager.setFrontendInfoList(infos);
+    }
+
+    private void setupDualTunerSetupForIsLowestPriority() {
+        // first clear the frontend resource to register new set of resources
+        clearFrontendInfoList();
+
+        TunerFrontendInfo[] infos = new TunerFrontendInfo[6];
+        // tunerFrontendInfo(handle, FrontendSettings.TYPE_*, exclusiveGroupId
+        infos[0] = tunerFrontendInfo(1, FrontendSettings.TYPE_DVBT, 1);
+        infos[1] = tunerFrontendInfo(2, FrontendSettings.TYPE_DVBC, 1);
+        infos[2] = tunerFrontendInfo(3, FrontendSettings.TYPE_DVBS, 1);
+        infos[3] = tunerFrontendInfo(4, FrontendSettings.TYPE_DVBT, 2);
+        infos[4] = tunerFrontendInfo(5, FrontendSettings.TYPE_DVBC, 2);
+        infos[5] = tunerFrontendInfo(6, FrontendSettings.TYPE_DVBS, 2);
+
+        mTunerResourceManager.setFrontendInfoList(infos);
+    }
+
+
+    private void testTwoClientsForIsLowestPriority(int prioA, int prioB) {
+
+        Tuner A = new Tuner(mContext, null, prioA);
+        Tuner B = new Tuner(mContext, null, prioB);
+
+        // all should return true
+        assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+
+        // let A hold resource
+        assignFeResource(A.getClientId(), FrontendSettings.TYPE_DVBT,
+                         true /* expectedResult */, 1 /* expectedHandle */);
+
+        // should return true for A as A is the sole holder
+        assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        // should return false for B only if A < B
+        if ( prioA < prioB ) {
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+        } else {
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+        }
+
+        A.close();
+        B.close();
+    }
+
+    private void testThreeClientsForIsLowestPriority(int prioA, int prioB, int prioC) {
+
+        Tuner A = new Tuner(mContext, null, prioA);
+        Tuner B = new Tuner(mContext, null, prioB);
+        Tuner C = new Tuner(mContext, null, prioC);
+
+        // all should return true
+        assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBC));
+        assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBS));
+
+        // let A & C hold resource
+        assignFeResource(A.getClientId(), FrontendSettings.TYPE_DVBT,
+                         true /* expectedResult */, 1 /* expectedHandle */);
+
+        assignFeResource(C.getClientId(), FrontendSettings.TYPE_DVBC,
+                         true /* expectedResult */, 5 /* expectedHandle */);
+
+        // should return false for B only if A < B
+        if (prioA > prioB && prioB > prioC) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA > prioC && prioC > prioB) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA > prioC && prioC > prioB) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioB > prioA && prioA > prioC) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioC > prioA && prioA > prioB) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioB > prioC && prioC > prioA) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioC > prioB && prioB > prioA) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA == prioB && prioB == prioC) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA == prioB && prioB > prioC) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA > prioB && prioB == prioC) {
+            assertFalse(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioA == prioC && prioC > prioB) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioB > prioA && prioA == prioC) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertTrue(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioC > prioA && prioA == prioB) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertTrue(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        } else if (prioB == prioC && prioC > prioA) {
+            assertTrue(A.isLowestPriority(FrontendSettings.TYPE_DVBC));
+            assertFalse(B.isLowestPriority(FrontendSettings.TYPE_DVBS));
+            assertFalse(C.isLowestPriority(FrontendSettings.TYPE_DVBT));
+        }
+
+        A.close();
+        B.close();
+        C.close();
+    }
+
+    @Test
+    public void testSharedFilterOneProcess() throws Exception {
+        Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+        assertTrue(f != null);
+
+        String token1 = f.acquireSharedFilterToken();
+        assertTrue(token1 != null);
+
+        String token2 = f.acquireSharedFilterToken();
+        assertTrue(token2 == null);
+
+        // Tune a frontend before start the filter
+        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);
+
+        Settings settings = SectionSettingsWithTableInfo
+                .builder(Filter.TYPE_TS)
+                .setTableId(2)
+                .setVersion(1)
+                .setCrcEnabled(true)
+                .setRaw(false)
+                .setRepeat(false)
+                .build();
+        FilterConfiguration config = TsFilterConfiguration
+                .builder()
+                .setTpid(10)
+                .setSettings(settings)
+                .build();
+
+        assertEquals(f.configure(config), Tuner.RESULT_INVALID_STATE);
+        assertEquals(f.setMonitorEventMask(Filter.MONITOR_EVENT_SCRAMBLING_STATUS),
+                Tuner.RESULT_INVALID_STATE);
+        assertEquals(f.setDataSource(null), Tuner.RESULT_INVALID_STATE);
+        assertEquals(f.start(), Tuner.RESULT_INVALID_STATE);
+        assertEquals(f.flush(), Tuner.RESULT_INVALID_STATE);
+        assertEquals(f.read(new byte[3], 0, 3), 0);
+        assertEquals(f.stop(), Tuner.RESULT_INVALID_STATE);
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        f.freeSharedFilterToken(token1);
+        f.close();
+        f = null;
+    }
+
+    @Test
+    public void testSharedFilterTwoProcessesCloseInSharedFilter() throws Exception {
+        String token = mSharedFilterTestServer.acquireSharedFilterToken();
+        assertTrue(token != null);
+        SharedFilter f =
+                Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+        assertTrue(f != null);
+
+        assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+        assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+        assertTrue(f.read(new byte[3], 0, 3) != 0);
+        assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+        mLockLatch = new CountDownLatch(1);
+        f.close();
+        f = null;
+        mSharedFilterTestServer.closeFilter();
+        Thread.sleep(2000);
+        assertEquals(mLockLatch.getCount(), 1);
+        mLockLatch = null;
+    }
+
+    @Test
+    public void testSharedFilterTwoProcessesCloseInFilter() throws Exception {
+        String token = mSharedFilterTestServer.acquireSharedFilterToken();
+        assertTrue(token != null);
+
+        SharedFilter f =
+                Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+        assertTrue(f != null);
+
+        assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+        assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+        assertTrue(f.read(new byte[3], 0, 3) != 0);
+        assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+        mLockLatch = new CountDownLatch(1);
+        mSharedFilterTestServer.closeFilter();
+        assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mLockLatch = null;
+        f.close();
+        f = null;
+    }
+
+    @Test
+    public void testSharedFilterTwoProcessesReleaseInFilter() throws Exception {
+        String token = mSharedFilterTestServer.acquireSharedFilterToken();
+        assertTrue(token != null);
+
+        SharedFilter f =
+                Tuner.openSharedFilter(mContext, token, getExecutor(), getSharedFilterCallback());
+        assertTrue(f != null);
+
+        assertEquals(f.start(), Tuner.RESULT_SUCCESS);
+        assertEquals(f.flush(), Tuner.RESULT_SUCCESS);
+        assertTrue(f.read(new byte[3], 0, 3) != 0);
+        assertEquals(f.stop(), Tuner.RESULT_SUCCESS);
+
+        mLockLatch = new CountDownLatch(1);
+        mSharedFilterTestServer.freeSharedFilterToken(token);
+        assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+        mLockLatch = null;
+
+        mSharedFilterTestServer.closeFilter();
+        f.close();
+        f = null;
+    }
+
+    @Test
+    public void testSharedFilterTwoProcessesVerifySharedFilter() throws Exception {
+        Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+        assertTrue(f != null);
+
+        String token = f.acquireSharedFilterToken();
+        assertTrue(token != null);
+
+        // Tune a frontend before start the shared filter
+        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);
+        assertTrue(mSharedFilterTestServer.verifySharedFilter(token));
+
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+
+        f.freeSharedFilterToken(token);
+        f.close();
+        f = null;
+    }
+
+    @Test
+    public void testFilterTimeDelay() throws Exception {
+        Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+
+        int timeDelayInMs = 5000;
+        Instant start = Instant.now();
+        int status = f.delayCallbackUntilMillisElapsed(timeDelayInMs);
+
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            // start / stop prevents initial race condition after first setting the time delay.
+            f.start();
+            f.stop();
+
+            mLockLatch = new CountDownLatch(1);
+            f.start();
+            assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+            Instant finish = Instant.now();
+            Duration timeElapsed = Duration.between(start, finish);
+            assertTrue(timeElapsed.toMillis() >= timeDelayInMs);
+        } else {
+            assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+        }
+        f.close();
+        f = null;
+    }
+
+    @Test
+    public void testFilterDataSizeDelay() throws Exception {
+        Filter f = createTsSectionFilter(mTuner, getExecutor(), getFilterCallback());
+        int status = f.delayCallbackUntilBytesAccumulated(5000);
+        if (TunerVersionChecker.isHigherOrEqualVersionTo(TunerVersionChecker.TUNER_VERSION_2_0)) {
+            assertEquals(Tuner.RESULT_SUCCESS, status);
+        } else {
+            assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+        }
+        f.close();
+    }
+
+    @Test
+    public void testMaxNumberOfFrontends() throws Exception {
+        List<Integer> ids = mTuner.getFrontendIds();
+        assertFalse(ids.isEmpty());
+        for (int i = 0; i < ids.size(); i++) {
+            int type = mTuner.getFrontendInfoById(ids.get(i)).getType();
+            if (TunerVersionChecker.isHigherOrEqualVersionTo(
+                        TunerVersionChecker.TUNER_VERSION_2_0)) {
+                int defaultMax = -1;
+                int status;
+                // Check default value
+                defaultMax = mTuner.getMaxNumberOfFrontends(type);
+                assertTrue(defaultMax > 0);
+                // Set to -1
+                status = mTuner.setMaxNumberOfFrontends(type, -1);
+                assertEquals(Tuner.RESULT_INVALID_ARGUMENT, status);
+                // Set to defaultMax + 1
+                status = mTuner.setMaxNumberOfFrontends(type, defaultMax + 1);
+                assertEquals(Tuner.RESULT_INVALID_ARGUMENT, status);
+                // Set to 0
+                status = mTuner.setMaxNumberOfFrontends(type, 0);
+                assertEquals(Tuner.RESULT_SUCCESS, status);
+                // Check after set
+                int currentMax = -1;
+                currentMax = mTuner.getMaxNumberOfFrontends(type);
+                assertEquals(currentMax, 0);
+                // Reset to default
+                status = mTuner.setMaxNumberOfFrontends(type, defaultMax);
+                assertEquals(Tuner.RESULT_SUCCESS, status);
+                currentMax = mTuner.getMaxNumberOfFrontends(type);
+                assertEquals(defaultMax, currentMax);
+            } else {
+                int defaultMax = mTuner.getMaxNumberOfFrontends(type);
+                assertEquals(defaultMax, -1);
+                int status = mTuner.setMaxNumberOfFrontends(type, 0);
+                assertEquals(Tuner.RESULT_UNAVAILABLE, status);
+            }
+        }
+    }
+
+    public static Filter createTsSectionFilter(
+            Tuner tuner, Executor e, FilterCallback cb) {
+        Filter f = tuner.openFilter(Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, e, cb);
+        Settings settings = SectionSettingsWithTableInfo
+                .builder(Filter.TYPE_TS)
+                .setTableId(2)
+                .setVersion(1)
+                .setCrcEnabled(true)
+                .setRaw(false)
+                .setRepeat(false)
+                .build();
+        FilterConfiguration config = TsFilterConfiguration
+                .builder()
+                .setTpid(10)
+                .setSettings(settings)
+                .build();
+        f.configure(config);
+        f.setMonitorEventMask(
+                Filter.MONITOR_EVENT_SCRAMBLING_STATUS | Filter.MONITOR_EVENT_IP_CID_CHANGE);
+
+        return f;
     }
 
     private boolean hasTuner() {
@@ -838,14 +2362,33 @@
                         testRestartEvent(filter, (RestartEvent) e);
                     }
                 }
+                if (mLockLatch != null) {
+                    mLockLatch.countDown();
+                }
             }
             @Override
             public void onFilterStatusChanged(Filter filter, int status) {}
         };
     }
 
+    private SharedFilterCallback getSharedFilterCallback() {
+        return new SharedFilterCallback() {
+            @Override
+            public void onFilterEvent(SharedFilter filter, FilterEvent[] events) {}
+            @Override
+            public void onFilterStatusChanged(SharedFilter filter, int status) {
+                if (status == SharedFilter.STATUS_INACCESSIBLE) {
+                    if (mLockLatch != null) {
+                        mLockLatch.countDown();
+                    }
+                }
+            }
+        };
+    }
+
     private void testDownloadEvent(Filter filter, DownloadEvent e) {
         e.getItemId();
+        e.getDownloadId();
         e.getMpuSequenceNumber();
         e.getItemFragmentIndex();
         e.getLastItemFragmentIndex();
@@ -868,6 +2411,8 @@
         e.getStreamId();
         e.isPtsPresent();
         e.getPts();
+        e.isDtsPresent();
+        e.getDts();
         e.getDataLength();
         e.getOffset();
         e.getLinearBlock();
@@ -876,6 +2421,7 @@
         e.getAudioHandle();
         e.getMpuSequenceNumber();
         e.isPrivateData();
+        e.getScIndexMask();
         AudioDescriptor ad = e.getExtraMetaData();
         if (ad != null) {
             ad.getAdFade();
@@ -917,7 +2463,8 @@
         e.getTableId();
         e.getVersion();
         e.getSectionNumber();
-        long length = e.getDataLength();
+        e.getDataLength();
+        long length = e.getDataLengthLong();
         if (length > 0) {
             byte[] buffer = new byte[(int) length];
             assertNotEquals(0, filter.read(buffer, 0, length));
@@ -969,10 +2516,10 @@
         };
     }
 
-    private FrontendSettings createFrontendSettings(FrontendInfo info) {
+    static public FrontendSettings createFrontendSettings(FrontendInfo info) {
             FrontendCapabilities caps = info.getFrontendCapabilities();
-            int minFreq = info.getFrequencyRange().getLower();
-            int maxFreq = info.getFrequencyRange().getUpper();
+            long minFreq = info.getFrequencyRangeLong().getLower();
+            long maxFreq = info.getFrequencyRangeLong().getUpper();
             FrontendCapabilities feCaps = info.getFrontendCapabilities();
             switch(info.getType()) {
                 case FrontendSettings.TYPE_ANALOG: {
@@ -981,7 +2528,7 @@
                     int sif = getFirstCapable(analogCaps.getSifStandardCapability());
                     return AnalogFrontendSettings
                             .builder()
-                            .setFrequency(minFreq)
+                            .setFrequencyLong(55250000) //2nd freq of VHF
                             .setSignalType(signalType)
                             .setSifStandard(sif)
                             .build();
@@ -993,11 +2540,11 @@
                     Atsc3FrontendSettings settings =
                             Atsc3FrontendSettings
                                     .builder()
-                                    .setFrequency(minFreq)
+                                    .setFrequencyLong(473000000) // 1st freq of UHF
                                     .setBandwidth(bandwidth)
                                     .setDemodOutputFormat(demod)
                                     .build();
-                    settings.setEndFrequency(maxFreq);
+                    settings.setEndFrequencyLong(maxFreq);
                     return settings;
                 }
                 case FrontendSettings.TYPE_ATSC: {
@@ -1005,7 +2552,7 @@
                     int modulation = getFirstCapable(atscCaps.getModulationCapability());
                     return AtscFrontendSettings
                             .builder()
-                            .setFrequency(minFreq)
+                            .setFrequencyLong(479000000) // 2nd freq of UHF
                             .setModulation(modulation)
                             .build();
                 }
@@ -1017,12 +2564,12 @@
                     DvbcFrontendSettings settings =
                             DvbcFrontendSettings
                                     .builder()
-                                    .setFrequency(minFreq)
+                                    .setFrequencyLong(490000000)
                                     .setModulation(modulation)
                                     .setInnerFec(fec)
                                     .setAnnex(annex)
                                     .build();
-                    settings.setEndFrequency(maxFreq);
+                    settings.setEndFrequencyLong(maxFreq);
                     return settings;
                 }
                 case FrontendSettings.TYPE_DVBS: {
@@ -1032,11 +2579,11 @@
                     DvbsFrontendSettings settings =
                             DvbsFrontendSettings
                                     .builder()
-                                    .setFrequency(minFreq)
+                                    .setFrequencyLong(950000000) //950Mhz
                                     .setModulation(modulation)
                                     .setStandard(standard)
                                     .build();
-                    settings.setEndFrequency(maxFreq);
+                    settings.setEndFrequencyLong(maxFreq);
                     return settings;
                 }
                 case FrontendSettings.TYPE_DVBT: {
@@ -1047,9 +2594,9 @@
                     int codeRate = getFirstCapable(dvbtCaps.getCodeRateCapability());
                     int hierarchy = getFirstCapable(dvbtCaps.getHierarchyCapability());
                     int guardInterval = getFirstCapable(dvbtCaps.getGuardIntervalCapability());
-                    return DvbtFrontendSettings
+                    DvbtFrontendSettings settings = DvbtFrontendSettings
                             .builder()
-                            .setFrequency(minFreq)
+                            .setFrequencyLong(498000000)
                             .setTransmissionMode(transmission)
                             .setBandwidth(bandwidth)
                             .setConstellation(constellation)
@@ -1060,28 +2607,34 @@
                             .setStandard(DvbtFrontendSettings.STANDARD_T)
                             .setMiso(false)
                             .build();
+                    settings.setEndFrequencyLong(maxFreq);
+                    return settings;
                 }
                 case FrontendSettings.TYPE_ISDBS3: {
                     Isdbs3FrontendCapabilities isdbs3Caps = (Isdbs3FrontendCapabilities) caps;
                     int modulation = getFirstCapable(isdbs3Caps.getModulationCapability());
                     int codeRate = getFirstCapable(isdbs3Caps.getCodeRateCapability());
-                    return Isdbs3FrontendSettings
+                    Isdbs3FrontendSettings settings = Isdbs3FrontendSettings
                             .builder()
-                            .setFrequency(minFreq)
+                            .setFrequencyLong(1000000000) //1000 Mhz
                             .setModulation(modulation)
                             .setCodeRate(codeRate)
                             .build();
+                    settings.setEndFrequencyLong(maxFreq);
+                    return settings;
                 }
                 case FrontendSettings.TYPE_ISDBS: {
                     IsdbsFrontendCapabilities isdbsCaps = (IsdbsFrontendCapabilities) caps;
                     int modulation = getFirstCapable(isdbsCaps.getModulationCapability());
                     int codeRate = getFirstCapable(isdbsCaps.getCodeRateCapability());
-                    return IsdbsFrontendSettings
+                    IsdbsFrontendSettings settings = IsdbsFrontendSettings
                             .builder()
-                            .setFrequency(minFreq)
+                            .setFrequencyLong(1050000000) //1050 Mhz
                             .setModulation(modulation)
                             .setCodeRate(codeRate)
                             .build();
+                    settings.setEndFrequencyLong(maxFreq);
+                    return settings;
                 }
                 case FrontendSettings.TYPE_ISDBT: {
                     IsdbtFrontendCapabilities isdbtCaps = (IsdbtFrontendCapabilities) caps;
@@ -1090,15 +2643,45 @@
                     int modulation = getFirstCapable(isdbtCaps.getModulationCapability());
                     int codeRate = getFirstCapable(isdbtCaps.getCodeRateCapability());
                     int guardInterval = getFirstCapable(isdbtCaps.getGuardIntervalCapability());
-                    return IsdbtFrontendSettings
-                            .builder()
-                            .setFrequency(minFreq)
-                            .setModulation(modulation)
-                            .setBandwidth(bandwidth)
-                            .setMode(mode)
-                            .setCodeRate(codeRate)
-                            .setGuardInterval(guardInterval)
-                            .build();
+                    int timeInterleaveMode =
+                            getFirstCapable(isdbtCaps.getTimeInterleaveModeCapability());
+                    boolean isSegmentAutoSupported = isdbtCaps.isSegmentAutoSupported();
+                    boolean isFullSegmentSupported = isdbtCaps.isFullSegmentSupported();
+
+                    IsdbtFrontendSettings.Builder builder = IsdbtFrontendSettings.builder();
+                    builder.setFrequencyLong(527143000); //22 ch    527.143 MHz
+                    builder.setBandwidth(bandwidth);
+                    builder.setMode(mode);
+                    builder.setGuardInterval(guardInterval);
+
+                    if (!TunerVersionChecker.isHigherOrEqualVersionTo(
+                                TunerVersionChecker.TUNER_VERSION_2_0)) {
+                        builder.setModulation(modulation);
+                        builder.setCodeRate(codeRate);
+                    } else {
+                        IsdbtFrontendSettings.IsdbtLayerSettings.Builder layerBuilder =
+                                IsdbtFrontendSettings.IsdbtLayerSettings.builder();
+                        layerBuilder.setTimeInterleaveMode(timeInterleaveMode);
+                        layerBuilder.setModulation(modulation);
+                        layerBuilder.setCodeRate(codeRate);
+                        if (isSegmentAutoSupported) {
+                            layerBuilder.setNumberOfSegments(0xFF);
+                        } else {
+                            if (isFullSegmentSupported) {
+                                layerBuilder.setNumberOfSegments(13);
+                            } else {
+                                layerBuilder.setNumberOfSegments(1);
+                            }
+                        }
+                        IsdbtFrontendSettings.IsdbtLayerSettings layer = layerBuilder.build();
+                        builder.setLayerSettings(
+                                new IsdbtFrontendSettings.IsdbtLayerSettings[] {layer});
+                        builder.setPartialReceptionFlag(
+                                IsdbtFrontendSettings.PARTIAL_RECEPTION_FLAG_TRUE);
+                    }
+                    IsdbtFrontendSettings settings = builder.build();
+                    settings.setEndFrequencyLong(maxFreq);
+                    return settings;
                 }
                 case FrontendSettings.TYPE_DTMB: {
                     DtmbFrontendCapabilities dtmbCaps = (DtmbFrontendCapabilities) caps;
@@ -1113,7 +2696,7 @@
                     DtmbFrontendSettings settings =
                             DtmbFrontendSettings
                                     .builder()
-                                    .setFrequency(minFreq)
+                                    .setFrequencyLong(506000000)
                                     .setModulation(modulation)
                                     .setTransmissionMode(transmissionMode)
                                     .setBandwidth(bandwidth)
@@ -1121,7 +2704,7 @@
                                     .setGuardInterval(guardInterval)
                                     .setTimeInterleaveMode(timeInterleaveMode)
                                     .build();
-                    settings.setEndFrequency(maxFreq);
+                    settings.setEndFrequencyLong(maxFreq);
                     return settings;
                 }
                 default:
@@ -1130,7 +2713,7 @@
         return null;
     }
 
-    private int getFirstCapable(int caps) {
+    static public int getFirstCapable(int caps) {
         if (caps == 0) return 0;
         int mask = 1;
         while ((mask & caps) == 0) {
@@ -1139,7 +2722,7 @@
         return (mask & caps);
     }
 
-    private long getFirstCapable(long caps) {
+    static public long getFirstCapable(long caps) {
         if (caps == 0) return 0;
         long mask = 1;
         while ((mask & caps) == 0) {
@@ -1158,6 +2741,13 @@
             }
 
             @Override
+            public void onUnlocked() {
+                if (mLockLatch != null) {
+                    mLockLatch.countDown();
+                }
+            }
+
+            @Override
             public void onScanStopped() {}
 
             @Override
@@ -1167,6 +2757,11 @@
             public void onFrequenciesReported(int[] frequency) {}
 
             @Override
+            public void onFrequenciesLongReported(long[] frequencies) {
+                ScanCallback.super.onFrequenciesLongReported(frequencies);
+            }
+
+            @Override
             public void onSymbolRatesReported(int[] rate) {}
 
             @Override
@@ -1217,6 +2812,80 @@
             public void onDvbcAnnexReported(int dvbcAnnext) {
                 ScanCallback.super.onDvbcAnnexReported(dvbcAnnext);
             }
+
+            @Override
+            public void onDvbtCellIdsReported(int[] dvbtCellIds) {
+                ScanCallback.super.onDvbtCellIdsReported(dvbtCellIds);
+            }
         };
     }
+
+    // TunerHandler utility for testing Tuner api calls in a different thread
+    private static final int MSG_TUNER_HANDLER_CREATE = 1;
+    private static final int MSG_TUNER_HANDLER_TUNE = 2;
+    private static final int MSG_TUNER_HANDLER_CLOSE = 3;
+
+    private ConditionVariable mTunerHandlerTaskComplete = new ConditionVariable();
+
+    private TunerHandler createTunerHandler(Looper looper) {
+        if (looper != null) {
+            return new TunerHandler(looper);
+        } else if ((looper = Looper.myLooper()) != null) {
+            return new TunerHandler(looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            return new TunerHandler(looper);
+        }
+        return null;
+    }
+
+    private class TunerHandler extends Handler {
+        Object mLock = new Object();
+        Tuner mHandlersTuner;
+        int mResult;
+
+        private TunerHandler(Looper looper) {
+            super(looper);
+        }
+
+        public Tuner getTuner() {
+            synchronized (mLock) {
+                return mHandlersTuner;
+            }
+        }
+
+        public int getResult() {
+            synchronized (mLock) {
+                return mResult;
+            }
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_TUNER_HANDLER_CREATE: {
+                    synchronized (mLock) {
+                        int useCase = msg.arg1;
+                        mHandlersTuner = new Tuner(mContext, null, useCase);
+                    }
+                    break;
+                }
+                case MSG_TUNER_HANDLER_TUNE: {
+                    synchronized (mLock) {
+                        FrontendSettings feSettings = (FrontendSettings) msg.obj;
+                        mResult = mHandlersTuner.tune(feSettings);
+                    }
+                    break;
+                }
+                case MSG_TUNER_HANDLER_CLOSE: {
+                    synchronized (mLock) {
+                        mHandlersTuner.close();
+                    }
+                    break;
+                }
+                default:
+                    break;
+            }
+            mTunerHandlerTaskComplete.open();
+        }
+    }
 }
diff --git a/tests/tests/uidmigration/Android.bp b/tests/tests/uidmigration/Android.bp
new file mode 100644
index 0000000..dc47946
--- /dev/null
+++ b/tests/tests/uidmigration/Android.bp
@@ -0,0 +1,39 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsSharedUserMigrationTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "compatibility-device-util-axt",
+        "androidx.test.rules",
+        "ctstestrunner-axt",
+        "permission-test-util-lib",
+    ],
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/uidmigration/AndroidManifest.xml b/tests/tests/uidmigration/AndroidManifest.xml
new file mode 100644
index 0000000..c3530da
--- /dev/null
+++ b/tests/tests/uidmigration/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="android.uidmigration.cts" >
+
+    <application android:label="SharedUserMigrationTest" />
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.uidmigration.cts"
+        android:label="CTS tests of android.uidmigration" >
+        <meta-data
+            android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/uidmigration/AndroidTest.xml b/tests/tests/uidmigration/AndroidTest.xml
new file mode 100644
index 0000000..5c7f26f
--- /dev/null
+++ b/tests/tests/uidmigration/AndroidTest.xml
@@ -0,0 +1,41 @@
+<?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 Shared UID Migration test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsSharedUserMigrationInstallTestApp.apk->/data/local/tmp/cts/uidmigration/InstallTestApp.apk" />
+        <option name="push" value="CtsSharedUserMigrationInstallTestAppRm.apk->/data/local/tmp/cts/uidmigration/InstallTestAppRm.apk" />
+        <option name="push" value="CtsSharedUserMigrationInstallTestAppMax.apk->/data/local/tmp/cts/uidmigration/InstallTestAppMax.apk" />
+        <option name="push" value="CtsSharedUserMigrationInstallTestApp2.apk->/data/local/tmp/cts/uidmigration/InstallTestApp2.apk" />
+        <option name="push" value="CtsSharedUserMigrationPermissionTestApp1.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp1.apk" />
+        <option name="push" value="CtsSharedUserMigrationPermissionTestApp2.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp2.apk" />
+        <option name="push" value="CtsSharedUserMigrationPermissionTestApp3.apk->/data/local/tmp/cts/uidmigration/PermissionTestApp3.apk" />
+        <option name="push" value="CtsSharedUserMigrationDataTestApp1.apk->/data/local/tmp/cts/uidmigration/DataTestApp1.apk" />
+        <option name="push" value="CtsSharedUserMigrationDataTestApp2.apk->/data/local/tmp/cts/uidmigration/DataTestApp2.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsSharedUserMigrationTestCases.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.uidmigration.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/uidmigration/DataTestApp/Android.bp b/tests/tests/uidmigration/DataTestApp/Android.bp
new file mode 100644
index 0000000..2acc29c
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/Android.bp
@@ -0,0 +1,42 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationDataTestApp1",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationDataTestApp2",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifest2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml b/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..9ec1d30
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?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.uidmigration.cts.DataTestApp"
+        android:sharedUserId="android.uidmigration.cts" >
+    <application>
+        <provider
+            android:name="android.uidmigration.cts.DataProvider"
+            android:authorities="android.uidmigration.cts.DataTestApp.provider"
+            android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml b/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml
new file mode 100644
index 0000000..d973efc
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/AndroidManifest2.xml
@@ -0,0 +1,37 @@
+<?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.uidmigration.cts.DataTestApp" >
+
+    <queries>
+        <package android:name="android.uidmigration.cts" />
+    </queries>
+
+    <application>
+        <provider
+            android:name="android.uidmigration.cts.DataProvider"
+            android:authorities="android.uidmigration.cts.DataTestApp.provider"
+            android:exported="true" />
+        <receiver
+            android:name="android.uidmigration.cts.UpdateReceiver"
+            android:exported="false" >
+            <intent-filter>
+                <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
+            </intent-filter>
+        </receiver>
+    </application>
+</manifest>
diff --git a/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.java b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.java
new file mode 100644
index 0000000..7ce4cf9
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/DataProvider.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.uidmigration.cts;
+
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+import java.util.UUID;
+
+public class DataProvider extends ContentProvider {
+
+    @Override
+    public boolean onCreate() {
+        return true;
+    }
+
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        Bundle data = new Bundle();
+        SharedPreferences prefs = getContext().getSharedPreferences("prefs", Context.MODE_PRIVATE);
+        String uuid = prefs.getString("uuid", null);
+        if (uuid == null) {
+            uuid = UUID.randomUUID().toString();
+            prefs.edit().putString("uuid", uuid).commit();
+        }
+        data.putString("uuid", uuid);
+        return data;
+    }
+
+    @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/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java
new file mode 100644
index 0000000..af529c7
--- /dev/null
+++ b/tests/tests/uidmigration/DataTestApp/src/android/uidmigration/cts/UpdateReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * 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.uidmigration.cts;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+
+public class UpdateReceiver extends BroadcastReceiver {
+
+    private static final String CTS_TEST_PKG = "android.uidmigration.cts";
+    private static final String ACTION_COUNTDOWN = "android.uidmigration.cts.ACTION_COUNTDOWN";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        String action = intent.getAction();
+        if (!Intent.ACTION_MY_PACKAGE_REPLACED.equals(action)) {
+            return;
+        }
+
+        // Notify the tester app
+        Intent i = new Intent(ACTION_COUNTDOWN);
+        i.setPackage(CTS_TEST_PKG);
+        i.putExtra(Intent.EXTRA_UID, Process.myUid());
+        context.sendBroadcast(i);
+    }
+}
diff --git a/tests/tests/uidmigration/InstallTestApp/Android.bp b/tests/tests/uidmigration/InstallTestApp/Android.bp
new file mode 100644
index 0000000..b0f06bc
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/Android.bp
@@ -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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationInstallTestApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationInstallTestApp2",
+    package_name: "android.uidmigration.cts.InstallTestApp2",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationInstallTestAppRm",
+    defaults: ["cts_defaults"],
+    manifest: "AndroidManifestRm.xml",
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationInstallTestAppMax",
+    defaults: ["cts_defaults"],
+    manifest: "AndroidManifestMax.xml",
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifest.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..c49500f
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifest.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="android.uidmigration.cts.InstallTestApp"
+        android:sharedUserId="android.uidmigration.cts" >
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.xml
new file mode 100644
index 0000000..e657934
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifestMax.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="android.uidmigration.cts.InstallTestApp"
+        android:sharedUserId="android.uidmigration.cts"
+        android:sharedUserMaxSdkVersion="30" >
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml b/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml
new file mode 100644
index 0000000..5b9eb27
--- /dev/null
+++ b/tests/tests/uidmigration/InstallTestApp/AndroidManifestRm.xml
@@ -0,0 +1,20 @@
+<?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.uidmigration.cts.InstallTestApp" >
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/OWNERS b/tests/tests/uidmigration/OWNERS
new file mode 100644
index 0000000..e69fd97
--- /dev/null
+++ b/tests/tests/uidmigration/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 315013
+topjohnwu@google.com
+ashfall@google.com
diff --git a/tests/tests/uidmigration/PermissionTestApp/Android.bp b/tests/tests/uidmigration/PermissionTestApp/Android.bp
new file mode 100644
index 0000000..2bdc777
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/Android.bp
@@ -0,0 +1,52 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationPermissionTestApp1",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationPermissionTestApp2",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifest2.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
+
+android_test_helper_app {
+    name: "CtsSharedUserMigrationPermissionTestApp3",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    manifest: "AndroidManifest3.xml",
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..5d6297e
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest.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="android.uidmigration.cts.PermissionTestApp"
+        android:sharedUserId="android.uidmigration.cts" >
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.xml
new file mode 100644
index 0000000..ac464c5
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest2.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.uidmigration.cts.PermissionTestApp.secondary"
+        android:sharedUserId="android.uidmigration.cts" >
+
+     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+     <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.xml b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.xml
new file mode 100644
index 0000000..791b354
--- /dev/null
+++ b/tests/tests/uidmigration/PermissionTestApp/AndroidManifest3.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="android.uidmigration.cts.PermissionTestApp.secondary" >
+
+     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+     <uses-permission android:name="android.permission.INTERNET" />
+
+    <application android:hasCode="false"/>
+</manifest>
diff --git a/tests/tests/uidmigration/TEST_MAPPING b/tests/tests/uidmigration/TEST_MAPPING
new file mode 100644
index 0000000..d63d02c
--- /dev/null
+++ b/tests/tests/uidmigration/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsSharedUserMigrationTestCases",
+      "options": [
+        {
+          "include-filter": "android.uidmigration.cts"
+        }
+      ]
+    }
+  ]
+}
diff --git a/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java b/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java
new file mode 100644
index 0000000..3be6eef
--- /dev/null
+++ b/tests/tests/uidmigration/src/android/uidmigration/cts/SharedUserMigrationTest.java
@@ -0,0 +1,313 @@
+/*
+ * 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.uidmigration.cts;
+
+import static android.Manifest.permission.INTERNET;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.permission.cts.PermissionUtils.grantPermission;
+import static android.permission.cts.PermissionUtils.isPermissionGranted;
+
+import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
+
+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.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.PackageInfoFlags;
+import android.net.Uri;
+import android.os.Bundle;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class SharedUserMigrationTest {
+
+    private static final String TMP_APK_PATH = "/data/local/tmp/cts/uidmigration";
+    private static final String INSTALL_TEST_PKG = "android.uidmigration.cts.InstallTestApp";
+    private static final String PERM_TEST_PKG = "android.uidmigration.cts.PermissionTestApp";
+    private static final String DATA_TEST_PKG = "android.uidmigration.cts.DataTestApp";
+    private static final String ACTION_COUNTDOWN = "android.uidmigration.cts.ACTION_COUNTDOWN";
+
+    private static final Throwable NOT_AN_ERROR = new Throwable();
+
+    private Context mContext;
+    private PackageManager mPm;
+
+    @Before
+    public void setup() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mPm = mContext.getPackageManager();
+    }
+
+    @After
+    public void tearDown() {
+        uninstallPackage(INSTALL_TEST_PKG);
+        uninstallPackage(INSTALL_TEST_PKG + "2");
+        uninstallPackage(PERM_TEST_PKG);
+        uninstallPackage(PERM_TEST_PKG + ".secondary");
+        uninstallPackage(DATA_TEST_PKG);
+    }
+
+    @Test
+    public void testAppInstall() throws PackageManager.NameNotFoundException {
+        String apk = TMP_APK_PATH + "/InstallTestApp";
+        assertTrue(installPackage(apk + ".apk"));
+        assertTrue(installPackage(apk + "2.apk"));
+
+        // Both app should share the same UID
+        int uid = mPm.getPackageUid(INSTALL_TEST_PKG, PackageInfoFlags.of(0));
+        String[] pkgs = mPm.getPackagesForUid(uid);
+        assertNotNull(pkgs);
+        assertEquals(2, pkgs.length);
+
+        // Leave shared UID by removing sharedUserId
+        assertTrue(installPackage(apk + "Rm.apk"));
+        pkgs = mPm.getPackagesForUid(uid);
+        assertNotNull(pkgs);
+        assertEquals(1, pkgs.length);
+
+        // Uninstall and reinstall the old app
+        uninstallPackage(INSTALL_TEST_PKG);
+        assertTrue(installPackage(apk + ".apk"));
+        pkgs = mPm.getPackagesForUid(uid);
+        assertNotNull(pkgs);
+        assertEquals(2, pkgs.length);
+
+        // Leave shared UID with sharedUserMaxSdkVersion
+        assertTrue(installPackage(apk + "Max.apk"));
+        pkgs = mPm.getPackagesForUid(uid);
+        assertNotNull(pkgs);
+        assertEquals(1, pkgs.length);
+
+        uninstallPackage(INSTALL_TEST_PKG);
+        uninstallPackage(INSTALL_TEST_PKG + "2");
+    }
+
+    @Test
+    public void testPermissionMigration() throws Exception {
+        String apk = TMP_APK_PATH + "/PermissionTestApp";
+        assertTrue(installPackage(apk + "1.apk"));
+        assertTrue(installPackage(apk + "2.apk"));
+
+        String secondaryPkg = PERM_TEST_PKG + ".secondary";
+
+        // Runtime permissions are not granted by default
+        assertFalse(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+        // Grant a runtime permission
+        grantPermission(secondaryPkg, WRITE_EXTERNAL_STORAGE);
+
+        // All apps in the UID group should have the same permissions
+        assertTrue(isPermissionGranted(PERM_TEST_PKG, INTERNET));
+        assertTrue(isPermissionGranted(PERM_TEST_PKG, WRITE_EXTERNAL_STORAGE));
+        assertTrue(isPermissionGranted(secondaryPkg, INTERNET));
+        assertTrue(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+        // Upgrade and leave shared UID
+        assertTrue(installPackage(apk + "3.apk"));
+
+        // The app in the original UID group should no longer have the permissions
+        assertFalse(isPermissionGranted(PERM_TEST_PKG, INTERNET));
+        assertFalse(isPermissionGranted(PERM_TEST_PKG, WRITE_EXTERNAL_STORAGE));
+
+        // The upgraded app should still have the permissions
+        assertTrue(isPermissionGranted(secondaryPkg, INTERNET));
+        assertTrue(isPermissionGranted(secondaryPkg, WRITE_EXTERNAL_STORAGE));
+
+        uninstallPackage(PERM_TEST_PKG);
+        uninstallPackage(secondaryPkg);
+    }
+
+    @Test
+    public void testDataMigration() throws PackageManager.NameNotFoundException {
+        String apk = TMP_APK_PATH + "/DataTestApp";
+        assertTrue(installPackage(apk + "1.apk"));
+        int oldUid = mPm.getPackageUid(DATA_TEST_PKG, PackageInfoFlags.of(0));
+
+        String authority = DATA_TEST_PKG + ".provider";
+        ContentResolver resolver = mContext.getContentResolver();
+
+        // Ask the app to generate a new random UUID and persist in data
+        Bundle result = resolver.call(authority, "", null, null);
+        assertNotNull(result);
+        String oldUUID = result.getString("uuid");
+        assertNotNull(oldUUID);
+
+        // Need 2 receivers because the intent filters are not compatible
+        PackageBroadcastVerifier verifier = new PackageBroadcastVerifier(oldUid);
+        BroadcastReceiver r1 = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                verifier.verify(intent);
+            }
+        };
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+        filter.addDataScheme("package");
+        mContext.registerReceiver(r1, filter);
+
+        BroadcastReceiver r2 = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                verifier.verify(intent);
+            }
+        };
+        filter = new IntentFilter();
+        filter.addAction(Intent.ACTION_UID_REMOVED);
+        filter.addAction(ACTION_COUNTDOWN);
+        mContext.registerReceiver(r2, filter);
+
+        // Update the data test APK and make sure UID changed
+        assertTrue(installPackage(apk + "2.apk"));
+        int newUid = mPm.getPackageUid(DATA_TEST_PKG, PackageInfoFlags.of(0));
+        assertNotEquals(oldUid, newUid);
+
+        // Ensure system broadcasts are delivered properly
+        try {
+            final Throwable e = verifier.poll(5, TimeUnit.SECONDS);
+            if (e != NOT_AN_ERROR) {
+                throw new AssertionError(e);
+            }
+        } catch (InterruptedException e) {
+            fail(e.getMessage());
+        }
+        assertEquals(newUid, verifier.newUid);
+
+        mContext.unregisterReceiver(r1);
+        mContext.unregisterReceiver(r2);
+
+        // Ask the app again for a UUID. If data migration is working, it shall be the same
+        result = resolver.call(authority, "", null, null);
+        assertNotNull(result);
+        String newUUID = result.getString("uuid");
+        assertEquals(oldUUID, newUUID);
+
+        uninstallPackage(DATA_TEST_PKG);
+    }
+
+    private boolean installPackage(String apkPath) {
+        return runShellCommand("pm install --force-queryable -t " + apkPath).equals("Success\n");
+    }
+
+    private void uninstallPackage(String packageName) {
+        runShellCommand("pm uninstall " + packageName);
+    }
+
+    static class PackageBroadcastVerifier extends ArrayBlockingQueue<Throwable> {
+
+        public int newUid = -1;
+
+        private final int mPreviousUid;
+        private int mCounter = 0;
+
+        PackageBroadcastVerifier(int uid) {
+            super(1);
+            mPreviousUid = uid;
+        }
+
+        void verify(Intent intent) {
+            try {
+                verifyInternal(intent);
+            } catch (Throwable e) {
+                offer(e);
+            }
+        }
+
+        private void verifyInternal(Intent intent) {
+            String action = intent.getAction();
+            assertNotNull(action);
+
+            if (action.equals(Intent.ACTION_UID_REMOVED)) {
+                // Not the test package, none of our business
+                if (intent.getIntExtra(Intent.EXTRA_UID, -1) != mPreviousUid) {
+                    return;
+                }
+            }
+
+            Uri data = intent.getData();
+            if (data != null) {
+                assertEquals("package", data.getScheme());
+                String pkg = data.getSchemeSpecificPart();
+                assertNotNull(pkg);
+                // Not the test package, none of our business
+                if (!DATA_TEST_PKG.equals(pkg)) {
+                    return;
+                }
+            }
+
+            // Broadcasts must come in the following order:
+            // ACTION_PACKAGE_REMOVED -> ACTION_UID_REMOVED
+            // -> ACTION_PACKAGE_ADDED -> ACTION_COUNTDOWN
+
+            mCounter++;
+
+            switch (action) {
+                case Intent.ACTION_PACKAGE_REMOVED:
+                    assertEquals(1, mCounter);
+                    assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+                    assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+                    assertEquals(mPreviousUid, intent.getIntExtra(Intent.EXTRA_UID, -1));
+                    break;
+                case Intent.ACTION_UID_REMOVED:
+                    assertEquals(2, mCounter);
+                    assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+                    assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+                    assertEquals(DATA_TEST_PKG, intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME));
+                    break;
+                case Intent.ACTION_PACKAGE_ADDED:
+                    assertEquals(3, mCounter);
+                    assertFalse(intent.hasExtra(Intent.EXTRA_REPLACING));
+                    assertTrue(intent.getBooleanExtra(Intent.EXTRA_UID_CHANGING, false));
+                    newUid = intent.getIntExtra(Intent.EXTRA_UID, mPreviousUid);
+                    assertNotEquals(mPreviousUid, newUid);
+                    assertEquals(mPreviousUid, intent.getIntExtra(Intent.EXTRA_PREVIOUS_UID, -1));
+                    break;
+                case ACTION_COUNTDOWN:
+                    assertEquals(4, mCounter);
+                    assertEquals(newUid, intent.getIntExtra(Intent.EXTRA_UID, -2));
+                    // End of actions
+                    offer(NOT_AN_ERROR);
+                    break;
+                case Intent.ACTION_PACKAGE_REPLACED:
+                    fail("PACKAGE_REPLACED should not be called!");
+                    break;
+                default:
+                    fail("Unknown action received");
+                    break;
+            }
+        }
+    }
+}
diff --git a/tests/tests/uirendering/res/layout/draw_order.xml b/tests/tests/uirendering/res/layout/draw_order.xml
new file mode 100644
index 0000000..0ca1efc
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/draw_order.xml
@@ -0,0 +1,30 @@
+<?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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="@dimen/test_width"
+             android:layout_height="@dimen/test_height">
+    <View
+        android:id="@+id/blueview"
+        android:layout_width="@dimen/test_width"
+        android:layout_height="@dimen/test_height"
+        android:background="#0000FF" />
+    <View
+        android:id="@+id/greenview"
+        android:layout_width="@dimen/test_width"
+        android:layout_height="@dimen/test_height"
+        android:background="#00FF00" />
+</FrameLayout>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
new file mode 100644
index 0000000..c05a3ce
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/AntiAliasingVerifier.java
@@ -0,0 +1,74 @@
+/*
+ * 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.uirendering.cts.bitmapverifiers;
+
+import android.graphics.Rect;
+import android.uirendering.cts.util.CompareUtils;
+
+/**
+ * Tests to see if there is rectangle of a given color, anti-aliased. It checks this by
+ * verifying that a nonzero number of pixels in the area lie strictly between the inner
+ * and outer colors (eg, they are a blend of the two). To verify that a rectangle is
+ * correctly rendered overall, create a RegionVerifier with one Rect to cover the inner
+ * region and another to cover the border region.
+ *
+ * Note that AA is tested by matching the final color against a blend of the inner/outer
+ * colors. To ensure correctness in this test, callers should simplify the test to include
+ * simple colors, eg, Black/Blue and no use of White (which includes colors in all channels).
+ */
+public class AntiAliasingVerifier extends PerPixelBitmapVerifier {
+    private int mOuterColor;
+    private int mInnerColor;
+    private Rect mBorderRect;
+    private int mVerifiedAAPixels = 0;
+
+    public AntiAliasingVerifier(int outerColor, int innerColor, Rect borderRect) {
+        // Zero tolerance since we use failures as signal to test for AA pixels
+        this(outerColor, innerColor, borderRect, 0);
+    }
+
+    public AntiAliasingVerifier(int outerColor, int innerColor, Rect borderRect, int tolerance) {
+        super(tolerance);
+        mOuterColor = outerColor;
+        mInnerColor = innerColor;
+        mBorderRect = borderRect;
+    }
+
+    @Override
+    protected int getExpectedColor(int x, int y) {
+        return mBorderRect.contains(x, y) ? mInnerColor : mOuterColor;
+    }
+
+    @Override
+    public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
+        boolean result = super.verify(bitmap, offset, stride, width, height);
+        // At a minimum, all but maybe the two end pixels on the left should be AA
+        result &= (mVerifiedAAPixels > (mBorderRect.height() - 2));
+        return result;
+    }
+
+    protected boolean verifyPixel(int x, int y, int observedColor) {
+        boolean result = super.verifyPixel(x, y, observedColor);
+        if (!result) {
+            result = CompareUtils.verifyPixelBetweenColors(observedColor, mOuterColor, mInnerColor);
+            if (result) {
+                ++mVerifiedAAPixels;
+            }
+        }
+        return result;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
index b007698..84f0803 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/AImageDecoderTest.kt
@@ -32,6 +32,8 @@
 import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier
 import android.uirendering.cts.bitmapverifiers.RectVerifier
 import android.uirendering.cts.bitmapverifiers.RegionVerifier
+import android.uirendering.cts.differencevisualizers.PassFailVisualizer
+import android.uirendering.cts.util.BitmapDumper
 import junitparams.JUnitParamsRunner
 import junitparams.Parameters
 import org.junit.Test
@@ -53,6 +55,8 @@
     private val ANDROID_IMAGE_DECODER_FINISHED = -10
     private val ANDROID_IMAGE_DECODER_INVALID_STATE = -11
 
+    private val DEBUG_CAPTURE_IMAGES = false
+
     private fun getAssets(): AssetManager {
         return InstrumentationRegistry.getTargetContext().getAssets()
     }
@@ -144,6 +148,7 @@
      * @param mssimThreshold The minimum MSSIM value to accept as similar. Some
      *                       images do not match exactly, but they've been
      *                       manually verified to look the same.
+     * @param testName Optional name of the calling test for BitmapDumper.
      */
     private fun decodeAndCropFrames(
         image: String,
@@ -151,7 +156,8 @@
         numFrames: Int,
         scaleFactor: Float,
         crop: Crop,
-        mssimThreshold: Double
+        mssimThreshold: Double,
+        testName: String = ""
     ) {
         val decodeAndCropper = DecodeAndCropper(image, scaleFactor, crop)
         var expectedBm = decodeAndCropper.bitmap
@@ -176,7 +182,13 @@
         while (true) {
             nDecode(decoder, testBm, ANDROID_IMAGE_DECODER_SUCCESS)
             val verifier = GoldenImageVerifier(expectedBm, MSSIMComparer(mssimThreshold))
-            assertTrue(verifier.verify(testBm), "$image has mismatch in frame $i")
+            if (!verifier.verify(testBm)) {
+                if (DEBUG_CAPTURE_IMAGES) {
+                    BitmapDumper.dumpBitmaps(expectedBm, testBm, "$testName(${image}_$i)",
+                            "AImageDecoderTest", PassFailVisualizer());
+                }
+                fail("$image has mismatch in frame $i")
+            }
             expectedBm.recycle()
 
             i++
@@ -216,7 +228,11 @@
     @Test
     @Parameters(method = "animationsAndFrames")
     fun testDecodeFramesScaleDown(image: String, frameName: String, numFrames: Int) {
-        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749)
+        // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+        // meaningless. It has been manually verified.
+        if (image == "alphabetAnim.gif") return
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.None, .749,
+                "testDecodeFramesScaleDown")
     }
 
     @Test
@@ -240,7 +256,11 @@
     @Test
     @Parameters(method = "animationsAndFrames")
     fun testDecodeFramesAndCropTopScaleDown(image: String, frameName: String, numFrames: Int) {
-        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749)
+        // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+        // meaningless. It has been manually verified.
+        if (image == "alphabetAnim.gif") return
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Top, .749,
+                "testDecodeFramesAndCropTopScaleDown")
     }
 
     @Test
@@ -264,7 +284,11 @@
     @Test
     @Parameters(method = "animationsAndFrames")
     fun testDecodeFramesAndCropLeftScaleDown(image: String, frameName: String, numFrames: Int) {
-        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596)
+        // Perceptually, this image looks reasonable, but the MSSIM is low enough to be
+        // meaningless. It has been manually verified.
+        if (image == "alphabetAnim.gif") return
+        decodeAndCropFrames(image, frameName, numFrames, .5f, Crop.Left, .596,
+                "testDecodeFramesAndCropLeftScaleDown")
     }
 
     @Test
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
index 907c88a..912004a 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapTests.java
@@ -28,6 +28,7 @@
 import android.graphics.Picture;
 import android.graphics.Rect;
 import android.os.Handler;
+import android.os.Looper;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
@@ -49,6 +50,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.Arrays;
+import java.util.function.Function;
 
 @LargeTest
 @RunWith(AndroidJUnit4.class)
@@ -88,6 +90,7 @@
     public void testChangeDuringRtAnimation() {
         class RtOnlyFrameCounter implements Window.OnFrameMetricsAvailableListener {
             private int count = 0;
+            Function<Integer, Void> onCountChanged = null;
 
             @Override
             public void onFrameMetricsAvailable(Window window, FrameMetrics frameMetrics,
@@ -96,6 +99,9 @@
                         && frameMetrics.getMetric(FrameMetrics.INPUT_HANDLING_DURATION) == 0
                         && frameMetrics.getMetric(FrameMetrics.LAYOUT_MEASURE_DURATION) == 0) {
                     count++;
+                    if (onCountChanged != null) {
+                        onCountChanged.apply(count);
+                    }
                 };
             }
 
@@ -121,19 +127,15 @@
                 mAnimator.setDuration(3000);
                 mAnimator.start();
 
-                Handler handler = new Handler();
-                handler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
+                Handler handler = new Handler(Looper.getMainLooper());
+                counter.onCountChanged = (Integer count) -> {
+                    // Ensure we've produced a couple frames and should now be in the RenderThread
+                    // animation. So bitmap changes shouldn't be observed.
+                    if (count >= 2) {
                         child.setColor(Color.RED);
-                        try {
-                            Thread.sleep(1000);
-                        } catch (Exception e) {
-                            // do nothing
-                        }
-                        child.setColor(Color.BLUE);
                     }
-                }, 1000);
+                    return null;
+                };
                 getActivity().getWindow().addOnFrameMetricsAvailableListener(counter, handler);
             }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
index de5da22..e9e88c9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/CanvasTests.java
@@ -32,6 +32,7 @@
 import android.graphics.RadialGradient;
 import android.graphics.Rect;
 import android.graphics.RectF;
+import android.graphics.Region;
 import android.graphics.Shader;
 import android.uirendering.cts.R;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
@@ -837,6 +838,60 @@
     }
 
     @Test
+    public void testClipIntersectAndDifference() {
+        Point[] testPoints = {
+            new Point(1, 1),
+            new Point(10, 10),
+            new Point(25, 25)
+        };
+        int[] colors = {
+            Color.WHITE,
+            Color.RED,
+            Color.GREEN
+        };
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    // Base is white
+                    Paint paint = new Paint();
+                    paint.setColor(Color.WHITE);
+                    canvas.drawRect(0, 0, width, height, paint);
+
+                    // Fill inset with green, which will later be overwitten with
+                    // red except for a subsequent difference clip op
+                    canvas.clipRect(5, 5, width - 5, height - 5);
+                    paint.setColor(Color.GREEN);
+                    canvas.drawRect(0, 0, width, height, paint);
+
+                    // Cut out the inner region, so that it remains green
+                    canvas.clipOutRect(20, 20, width - 20, height - 20);
+                    paint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, paint);
+                })
+                .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+    }
+
+    // Expanding region ops (replace, reverse diff, union, and xor) are not allowed for clipping
+    @Test(expected = IllegalArgumentException.class)
+    public void testClipReplace() {
+        new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REPLACE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClipReverseDifference() {
+        new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.REVERSE_DIFFERENCE);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClipUnion() {
+        new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.UNION);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testClipXor() {
+        new Canvas(getMutableBitmap()).clipRect(0, 0, 10, 10, Region.Op.XOR);
+    }
+
+    @Test
     public void testAntiAliasClipping() {
         createTest()
                 .addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java
new file mode 100644
index 0000000..97d6a67
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/DrawingOrderTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+import android.graphics.Color;
+import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class DrawingOrderTest extends ActivityTestBase {
+
+    @Test
+    public void testDefaultDrawOrder() {
+        createTest().addLayout(R.layout.draw_order, null)
+                .runWithVerifier(new ColorVerifier(Color.GREEN));
+    }
+
+    @Test
+    public void testTranslationZOrder() {
+        createTest().addLayout(R.layout.draw_order, (ViewInitializer) view -> {
+            view.findViewById(R.id.blueview).setTranslationZ(4);
+            view.findViewById(R.id.greenview).setTranslationZ(0);
+        }).runWithVerifier(new ColorVerifier(Color.BLUE));
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java
new file mode 100644
index 0000000..11e8b56
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/Nulls.java
@@ -0,0 +1,30 @@
+/*
+ * 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.uirendering.cts.testclasses;
+
+/** Indirection for the Java flow analysis to allow passing `null` for @Nonnull parameters. */
+public class Nulls {
+    @SuppressWarnings("null")
+    public static <T> T type() {
+        return null;
+    }
+
+    @SuppressWarnings("null")
+    public static <T> T[] array() {
+        return null;
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
index bed338c..ae79f4c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RenderNodeTests.java
@@ -36,6 +36,7 @@
 import android.graphics.Rect;
 import android.graphics.RenderEffect;
 import android.graphics.RenderNode;
+import android.graphics.RuntimeShader;
 import android.graphics.Shader;
 import android.graphics.drawable.Drawable;
 import android.uirendering.cts.R;
@@ -872,6 +873,42 @@
             );
     }
 
+    private static String sRedBlueInversionShader = ""
+            + "uniform shader inputShader;"
+            +  "vec4 main(vec2 coord) { "
+            + "  vec4 color = inputShader.eval(coord);"
+            + "  return vec4(color.b, color.g, color.r, color.a);"
+            + "}";
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRuntimeShaderRenderEffectInvalidUinformName() {
+        RuntimeShader shader = new RuntimeShader(sRedBlueInversionShader);
+        RenderEffect runtimeEffect = RenderEffect.createRuntimeShaderEffect(
+                shader, "invalidUniformName");
+    }
+
+    @Test
+    public void testRuntimeShaderRenderEffect() {
+        RuntimeShader shader = new RuntimeShader(sRedBlueInversionShader);
+        RenderEffect runtimeEffect = RenderEffect.createRuntimeShaderEffect(
+                shader, "inputShader");
+        final RenderNode renderNode = new RenderNode(null);
+        renderNode.setRenderEffect(runtimeEffect);
+        renderNode.setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT);
+        {
+            Canvas recordingCanvas = renderNode.beginRecording();
+            Paint paint = new Paint();
+            paint.setColor(Color.BLUE);
+            recordingCanvas.drawRect(0, 0, TEST_WIDTH, TEST_HEIGHT, paint);
+            renderNode.endRecording();
+        }
+
+        createTest()
+                .addCanvasClientWithoutUsingPicture((canvas, width, height) -> {
+                    canvas.drawRenderNode(renderNode);
+                }, true)
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
 
     @Test
     public void testBlurShaderLargeRadiiEdgeReplication() {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
new file mode 100644
index 0000000..0358364
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
@@ -0,0 +1,508 @@
+/*
+ * 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.uirendering.cts.testclasses
+
+import android.graphics.Bitmap
+import android.graphics.BitmapShader
+import android.graphics.BlendMode
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.graphics.ComposeShader
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.graphics.Picture
+import android.graphics.Rect
+import android.graphics.RuntimeShader
+import android.graphics.Shader
+import android.uirendering.cts.bitmapverifiers.RectVerifier
+import android.uirendering.cts.testinfrastructure.ActivityTestBase
+import android.uirendering.cts.testinfrastructure.CanvasClient
+import android.util.Half
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import org.junit.Assert
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class RuntimeShaderTests : ActivityTestBase() {
+
+    @Test(expected = NullPointerException::class)
+    fun createWithNullInput() {
+        RuntimeShader(Nulls.type<String>())
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun createWithEmptyInput() {
+        RuntimeShader("")
+    }
+
+    val bitmapShader = BitmapShader(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888),
+                                    Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+
+    @Test(expected = NullPointerException::class)
+    fun setNullUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setFloatUniform(Nulls.type<String>(), 0.0f)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setEmptyUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setFloatUniform("", 0.0f)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setFloatUniform("invalid", 0.0f)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidUniformType() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setFloatUniform("inputInt", 1.0f)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidUniformLength() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setFloatUniform("inputNonColor", 1.0f, 1.0f, 1.0f)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullIntUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setIntUniform(Nulls.type<String>(), 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setEmptyIntUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setIntUniform("", 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidIntUniformName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setIntUniform("invalid", 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidIntUniformType() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setIntUniform("inputFloat", 1)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidIntUniformLength() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setIntUniform("inputInt", 1, 2)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullColorName() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform(Nulls.type<String>(), 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setEmptyColorName() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform("", 0)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidColorName() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform("invalid", 0)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullColorValue() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform("inputColor", Nulls.type<Color>())
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setColorValueNonColorUniform() {
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform("inputNonColor", Color.BLUE)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullShaderName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputShader(Nulls.type<String>(), bitmapShader)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setEmptyShaderName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputShader("", bitmapShader)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidShaderName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputShader("invalid", bitmapShader)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullShaderValue() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputShader("inputShader", Nulls.type<Shader>())
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullBufferName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputBuffer(Nulls.type<String>(), bitmapShader)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setEmptyBufferName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputBuffer("", bitmapShader)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun setInvalidBufferName() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputBuffer("invalid", bitmapShader)
+    }
+
+    @Test(expected = NullPointerException::class)
+    fun setNullBufferValue() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputBuffer("inputShader", Nulls.type<BitmapShader>())
+    }
+
+    @Test
+    fun testDefaultUniform() {
+        val shader = RuntimeShader(simpleShader)
+        shader.setInputShader("inputShader", RuntimeShader(simpleRedShader))
+
+        val paint = Paint()
+        paint.shader = shader
+
+        val rect = Rect(10, 10, 80, 80)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+    }
+
+    @Test
+    fun testDefaultColorUniform() {
+        val shader = RuntimeShader(simpleColorShader, true)
+
+        val paint = Paint()
+        paint.shader = shader
+
+        val rect = Rect(10, 10, 80, 80)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+    }
+
+    @Test
+    fun testDefaultInputShader() {
+        val paint = Paint()
+        paint.color = Color.BLUE
+        paint.shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+
+        val rect = Rect(10, 10, 80, 80)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, paint.color, rect))
+    }
+
+    @Test
+    fun testDefaultInputShaderWithPaintAlpha() {
+        val paint = Paint()
+        paint.color = Color.argb(0.5f, 0.0f, 0.0f, 1.0f)
+        paint.shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+        paint.blendMode = BlendMode.SRC
+
+        val rect = Rect(10, 10, 80, 80)
+
+        // The shader should be evaluated with an opaque paint color and the paint's alpha will be
+        // applied after the shader returns but before it is blended into the destination
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, paint.color, rect))
+    }
+
+    @Test
+    fun testInputShaderWithPaintAlpha() {
+        val shader = RuntimeShader(mBlackIfInputNotOpaqueShader)
+        shader.setInputShader("inputShader", RuntimeShader(mSemiTransparentBlueShader))
+
+        val paint = Paint()
+        paint.color = Color.argb(0.5f, 0.0f, 1.0f, .0f)
+        paint.shader = shader
+        paint.blendMode = BlendMode.SRC
+
+        val rect = Rect(10, 10, 80, 80)
+
+        // The shader should be evaluated first then the paint's alpha will be applied after the
+        // shader returns but before it is blended into the destination
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLACK, rect))
+    }
+
+    @Test
+    fun testInputShaderWithFiltering() {
+        val bitmap = Bitmap.createBitmap(2, 2, Bitmap.Config.ARGB_8888)
+        bitmap.setPixel(0, 0, Color.RED)
+        bitmap.setPixel(1, 0, Color.BLUE)
+        bitmap.setPixel(0, 1, Color.BLUE)
+        bitmap.setPixel(1, 1, Color.RED)
+
+        val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+
+        // use slightly left than half to avoid any confusion on which pixel
+        // is sampled with FILTER_MODE_NEAREST
+        val matrix = Matrix()
+        matrix.postScale(0.49f, 0.49f)
+        bitmapShader.setLocalMatrix(matrix)
+
+        val shader = RuntimeShader(samplingShader)
+        shader.setInputShader("inputShader", bitmapShader)
+
+        val rect = Rect(0, 0, 1, 1)
+        val paint = Paint()
+
+        // The bitmap shader should be sampled with FILTER_MODE_NEAREST as the paint's filtering
+        // flag is not respected
+        paint.shader = shader
+        paint.blendMode = BlendMode.SRC
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+
+        // The bitmap shader should be sampled with FILTER_MODE_LINEAR as the paint's filtering
+        // flag is not respected
+        paint.isFilterBitmap = false
+        bitmapShader.filterMode = BitmapShader.FILTER_MODE_LINEAR
+        shader.setInputShader("inputShader", bitmapShader)
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE,
+                Color.valueOf(0.5f, 0.0f, 0.5f).toArgb(), rect))
+    }
+
+    @Test
+    fun testInputBuffer() {
+        val unpremulColor = Color.valueOf(0.75f, 0.0f, 1.0f, 0.5f)
+
+        // create a buffer and put an unpremul value into it
+        val srcBuf = ByteBuffer.allocate(8)
+        srcBuf.order(ByteOrder.LITTLE_ENDIAN)
+        srcBuf.putShort(Half(unpremulColor.red()).halfValue())
+        srcBuf.putShort(Half(unpremulColor.green()).halfValue())
+        srcBuf.putShort(Half(unpremulColor.blue()).halfValue())
+        srcBuf.putShort(Half(unpremulColor.alpha()).halfValue())
+        srcBuf.rewind()
+
+        // create a bitmap with the unpremul value and set a colorspace to something that is
+        // different from the destination
+        val bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGBA_F16,
+                true, ColorSpace.get(ColorSpace.Named.BT2020))
+        bitmap.copyPixelsFromBuffer(srcBuf)
+        bitmap.setPremultiplied(false)
+
+        val bitmapShader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
+        val shader = RuntimeShader(sampleComparisonShader)
+        shader.setFloatUniform("expectedSample", unpremulColor.components)
+
+        val rect = Rect(0, 0, 1, 1)
+        val paint = Paint()
+        paint.shader = shader
+        paint.blendMode = BlendMode.SRC
+
+        // Use setInputBuffer and let the shader verify that the sample contents were unaltered.
+        shader.setInputBuffer("inputShader", bitmapShader)
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.GREEN, rect))
+
+        // Use setInputShader to treating it like a normal bitmap instead of data to verify
+        // everything is working as expected
+        shader.setInputShader("inputShader", bitmapShader)
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+    }
+
+    @Test
+    fun testBasicColorUniform() {
+        val color = Color.valueOf(Color.BLUE).convert(ColorSpace.get(ColorSpace.Named.BT2020))
+        val shader = RuntimeShader(simpleColorShader)
+        shader.setColorUniform("inputColor", color)
+
+        val paint = Paint()
+        paint.shader = shader
+
+        val rect = Rect(10, 10, 80, 80)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLUE, rect))
+    }
+
+    @Test
+    fun testOpaqueConstructor() {
+        val shader = RuntimeShader(simpleColorShader, true)
+        Assert.assertTrue(shader.isForceOpaque)
+
+        val color = Color.valueOf(0.0f, 0.0f, 1.0f, 0.5f)
+        shader.setFloatUniform("inputNonColor", color.components)
+        shader.setIntUniform("useNonColor", 1)
+
+        val paint = Paint()
+        paint.shader = shader
+        paint.blendMode = BlendMode.SRC
+
+        val rect = Rect(10, 10, 80, 80)
+
+        createTest().addCanvasClient(CanvasClient
+                { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.BLUE, rect))
+    }
+
+    @Test
+    fun testDrawThroughPicture() {
+        val rect = Rect(10, 10, 80, 80)
+        val picture = Picture()
+        run {
+            val paint = Paint()
+            paint.shader = RuntimeShader(simpleRedShader)
+
+            val canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT)
+            canvas.clipRect(rect)
+            canvas.drawPaint(paint)
+            picture.endRecording()
+        }
+        Assert.assertTrue(picture.requiresHardwareAcceleration())
+
+        createTest().addCanvasClient(CanvasClient
+        { canvas: Canvas, width: Int, height: Int -> canvas.drawPicture(picture) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+    }
+
+    @Test
+    fun testDrawThroughPictureWithComposeShader() {
+        val rect = Rect(10, 10, 80, 80)
+        val picture = Picture()
+        run {
+            val paint = Paint()
+            val runtimeShader = RuntimeShader(simpleRedShader)
+            paint.shader = ComposeShader(runtimeShader, bitmapShader, BlendMode.DST)
+
+            val canvas = picture.beginRecording(TEST_WIDTH, TEST_HEIGHT)
+            canvas.clipRect(rect)
+            canvas.drawPaint(paint)
+            picture.endRecording()
+        }
+        Assert.assertTrue(picture.requiresHardwareAcceleration())
+
+        createTest().addCanvasClient(CanvasClient
+        { canvas: Canvas, width: Int, height: Int -> canvas.drawPicture(picture) },
+                true).runWithVerifier(RectVerifier(Color.WHITE, Color.RED, rect))
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testDrawIntoSoftwareCanvas() {
+        val paint = Paint()
+        paint.shader = RuntimeShader(simpleRedShader)
+
+        val canvas = Canvas(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+        canvas.drawRect(0f, 0f, 10f, 10f, paint)
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun testDrawIntoSoftwareCanvasWithComposeShader() {
+        val paint = Paint()
+        paint.shader = ComposeShader(RuntimeShader(simpleRedShader), bitmapShader, BlendMode.SRC)
+
+        val canvas = Canvas(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
+        canvas.drawRect(0f, 0f, 10f, 10f, paint)
+    }
+
+    val mSemiTransparentBlueShader = """
+        vec4 main(vec2 coord) {
+          return vec4(0.0, 0.0, 0.5, 0.5);
+        }"""
+    val simpleRedShader = """
+       vec4 main(vec2 coord) {
+          return vec4(1.0, 0.0, 0.0, 1.0);
+       }"""
+    val simpleColorShader = """
+        layout(color) uniform vec4 inputColor;
+        uniform vec4 inputNonColor;
+        uniform int useNonColor;
+       vec4 main(vec2 coord) {
+          vec4 outputColor = inputColor;
+          if (useNonColor != 0) {
+            outputColor = inputNonColor;
+          }
+          return outputColor;
+       }"""
+    val samplingShader = """
+        uniform shader inputShader;
+        vec4 main(vec2 coord) {
+          return inputShader.eval(coord).rgba;
+        }"""
+    val sampleComparisonShader = """
+        uniform shader inputShader;
+        uniform vec4 expectedSample;
+        vec4 main(vec2 coord) {
+          vec4 sampledValue = inputShader.eval(coord);
+          if (sampledValue == expectedSample) {
+            return vec4(0.0, 1.0, 0.0, 1.0);
+          }
+          return vec4(1.0, 0.0, 0.0, 1.0);
+        }"""
+    val simpleShader = """
+        uniform shader inputShader;
+        uniform float inputFloat;
+        uniform int inputInt;
+       vec4 main(vec2 coord) {
+          float alpha = float(100 - inputInt) / 100.0;
+          return vec4(inputShader.eval(coord).rgb * inputFloat, alpha);
+       }"""
+    val mBlackIfInputNotOpaqueShader = """
+        uniform shader inputShader;
+        vec4 main(vec2 coord) {
+          vec4 color = inputShader.eval(coord);
+          float multiplier = 1.0;
+          if (color.a != 1.0) {
+            multiplier = 0.0;
+          }
+          return vec4(color.rgb * multiplier, 1.0);
+        }"""
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
index 5defe4b..7fa1441 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
@@ -1,14 +1,16 @@
 package android.uirendering.cts.testclasses;
 
-import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 
 import android.graphics.Color;
 import android.graphics.Outline;
 import android.graphics.Path;
 import android.graphics.Rect;
 import android.uirendering.cts.R;
+import android.uirendering.cts.bitmapverifiers.AntiAliasingVerifier;
 import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
+import android.uirendering.cts.bitmapverifiers.RegionVerifier;
 import android.uirendering.cts.testclasses.view.UnclippedBlueView;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.ViewInitializer;
@@ -35,7 +37,12 @@
     static final Rect BOUNDS_RECT = new Rect(0, 0, 80, 80);
     static final Rect PADDED_RECT = new Rect(15, 16, 63, 62);
     static final Rect OUTLINE_RECT = new Rect(1, 2, 78, 79);
+    static final Rect ANTI_ALIAS_OUTLINE_RECT = new Rect(20, 10, 80, 80);
     static final Rect CLIP_BOUNDS_RECT = new Rect(10, 20, 50, 60);
+    static final Rect CONCAVE_OUTLINE_RECT1 = new Rect(0, 0, 10, 90);
+    static final Rect CONCAVE_TEST_RECT1 = new Rect(0, 10, 90, 90);
+    static final Rect CONCAVE_OUTLINE_RECT2 = new Rect(0, 0, 90, 10);
+    static final Rect CONCAVE_TEST_RECT2 = new Rect(10, 0, 90, 90);
 
     static final ViewInitializer BOUNDS_CLIP_INIT =
             rootView -> ((ViewGroup)rootView).setClipChildren(true);
@@ -49,6 +56,7 @@
 
     static final ViewInitializer OUTLINE_CLIP_INIT = rootView -> {
         View child = rootView.findViewById(R.id.child);
+//        ((ViewGroup)(child.getParent())).setBackgroundColor(Color.WHITE);
         child.setOutlineProvider(new ViewOutlineProvider() {
             @Override
             public void getOutline(View view, Outline outline) {
@@ -58,6 +66,48 @@
         child.setClipToOutline(true);
     };
 
+    static final ViewInitializer OUTLINE_CLIP_AA_INIT = rootView -> {
+        View child = rootView.findViewById(R.id.child);
+        ((ViewGroup) (child.getParent())).setBackgroundColor(Color.BLACK);
+        child.setOutlineProvider(new ViewOutlineProvider() {
+            Path mPath = new Path();
+            @Override
+            public void getOutline(View view, Outline outline) {
+                mPath.rewind();
+                // We're using the AA outline rect as a starting point, but shifting one of the
+                // vetices slightly to force AA for this not-quite-rectangle
+                mPath.moveTo(ANTI_ALIAS_OUTLINE_RECT.left, ANTI_ALIAS_OUTLINE_RECT.top);
+                mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.right, ANTI_ALIAS_OUTLINE_RECT.top);
+                mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.right, ANTI_ALIAS_OUTLINE_RECT.bottom);
+                mPath.lineTo(ANTI_ALIAS_OUTLINE_RECT.left + 1, ANTI_ALIAS_OUTLINE_RECT.bottom);
+                mPath.close();
+                outline.setPath(mPath);
+            }
+        });
+        child.setClipToOutline(true);
+    };
+
+    static final ViewInitializer CONCAVE_CLIP_INIT = rootView -> {
+        View child = rootView.findViewById(R.id.child);
+        ((ViewGroup) (child.getParent())).setBackgroundColor(Color.BLACK);
+        child.setOutlineProvider(new ViewOutlineProvider() {
+            Path mPath = new Path();
+            @Override
+            public void getOutline(View view, Outline outline) {
+                mPath.rewind();
+                mPath.addRect(CONCAVE_OUTLINE_RECT1.left, CONCAVE_OUTLINE_RECT1.top,
+                        CONCAVE_OUTLINE_RECT1.right, CONCAVE_OUTLINE_RECT1.bottom,
+                        Path.Direction.CW);
+                mPath.addRect(CONCAVE_OUTLINE_RECT2.left, CONCAVE_OUTLINE_RECT2.top,
+                        CONCAVE_OUTLINE_RECT2.right, CONCAVE_OUTLINE_RECT2.bottom,
+                        Path.Direction.CW);
+                outline.setPath(mPath);
+                assertTrue(outline.canClip());
+            }
+        });
+        child.setClipToOutline(true);
+    };
+
     static final ViewInitializer CLIP_BOUNDS_CLIP_INIT =
             view -> view.setClipBounds(CLIP_BOUNDS_RECT);
 
@@ -66,6 +116,18 @@
         return new RectVerifier(Color.WHITE, Color.BLUE, blueBoundsRect, 75);
     }
 
+    static BitmapVerifier makeConcaveClipVerifier() {
+        return new RegionVerifier()
+                .addVerifier(CONCAVE_TEST_RECT1, new RectVerifier(Color.BLACK, Color.BLUE,
+                        CONCAVE_OUTLINE_RECT1, 75))
+                .addVerifier(CONCAVE_TEST_RECT2, new RectVerifier(Color.BLACK, Color.BLUE,
+                        CONCAVE_OUTLINE_RECT2, 75));
+    }
+
+    static BitmapVerifier makeAAClipVerifier(Rect blueBoundsRect) {
+        return new AntiAliasingVerifier(Color.BLACK, Color.BLUE, blueBoundsRect);
+    }
+
     @Test
     public void testSimpleUnclipped() {
         createTest()
@@ -109,10 +171,16 @@
     }
 
     @Test
+    public void testAntiAliasedOutlineClip() {
+        // NOTE: Only HW is supported
+        createTest()
+                .addLayout(R.layout.blue_padded_layout, OUTLINE_CLIP_AA_INIT, true)
+                .runWithVerifier(makeAAClipVerifier(ANTI_ALIAS_OUTLINE_RECT));
+    }
+
+    @Test
     public void testOvalOutlineClip() {
-        // In hw this works because clipping to an arbitrary path (i.e. not a rectangle, round rect
-        // or circle) isn't supported, and is no-op'd.
-        // In sw this works because Outline clipping isn't supported.
+        // As of Android T, Outline clipping is enabled for all shapes.
         createTest()
                 .addLayout(R.layout.blue_padded_layout, view -> {
                     view.setOutlineProvider(new ViewOutlineProvider() {
@@ -123,10 +191,10 @@
                             mPath.addOval(0, 0, view.getWidth(), view.getHeight(),
                                     Path.Direction.CW);
                             outline.setPath(mPath);
-                            assertFalse(outline.canClip());
+                            assertTrue(outline.canClip());
                         }
                     });
-                    view.setClipToOutline(true); // should do nothing
+                    view.setClipToOutline(false); // should do nothing
                 })
                 .runWithVerifier(makeClipVerifier(FULL_RECT));
     }
@@ -137,23 +205,9 @@
         // path, but it does not result in clipping, which is only supported when explicitly calling
         // one of the other setters. (hw no-op's the arbitrary path, and sw doesn't support Outline
         // clipping.)
+        // As of T, path clipping is enabled for all Outline shapes.
         createTest()
-                .addLayout(R.layout.blue_padded_layout, view -> {
-                    view.setOutlineProvider(new ViewOutlineProvider() {
-                        Path mPath = new Path();
-                        @Override
-                        public void getOutline(View view, Outline outline) {
-                            mPath.reset();
-                            mPath.addRect(0, 0, 10, 100, Path.Direction.CW);
-                            mPath.addRect(0, 0, 100, 10, Path.Direction.CW);
-                            assertFalse(mPath.isConvex());
-
-                            outline.setPath(mPath);
-                            assertFalse(outline.canClip());
-                        }
-                    });
-                    view.setClipToOutline(true); // should do nothing
-                })
-                .runWithVerifier(makeClipVerifier(FULL_RECT));
+                .addLayout(R.layout.blue_padded_layout, CONCAVE_CLIP_INIT, true)
+                .runWithVerifier(makeConcaveClipVerifier());
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
index a5d40cf..7a91ab9 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.kt
@@ -82,6 +82,7 @@
 
         window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
                 View.SYSTEM_UI_FLAG_FULLSCREEN
+        window.decorView.keepScreenOn = true
         mHandler = RenderSpecHandler()
 
         setContentView(R.layout.test_container)
@@ -119,7 +120,6 @@
             } catch (e: InterruptedException) {
                 throw AssertionError(e)
             }
-
         }
         assertNotNull("Timeout waiting for draw", mPositionInfo)
         return mPositionInfo!!
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java b/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
index c80a778..de7d149 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/CompareUtils.java
@@ -25,4 +25,16 @@
                 && Math.abs(Color.green(color) - average) <= threshold
                 && Math.abs(Color.blue(color) - average) <= threshold;
     }
+
+    /**
+     * @return True if color strictly between inner and outer colors. This verifies that the
+     * color is a mixture of the two, not just one or the other (for anti-aliased pixels).
+     */
+    public static boolean verifyPixelBetweenColors(int color, int expectedOuterColor,
+            int expectedInnerColor) {
+        if (color == expectedInnerColor || color == expectedOuterColor) {
+            return false;
+        }
+        return color == ((color & expectedInnerColor) | (color & expectedOuterColor));
+    }
 }
diff --git a/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.java b/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.java
new file mode 100644
index 0000000..eceb056
--- /dev/null
+++ b/tests/tests/uirendering27/src/android/uirendering/cts/testclasses/ExpandingClipTest.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.uirendering.cts.testclasses;
+
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Region;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ExpandingClipTest extends ActivityTestBase {
+
+    @Test
+    public void testClipReplace() {
+        // The replace op should function correctly on apps compatible with version 27
+        Point[] testPoints = {
+            new Point(0, 5),
+            new Point(10, 5)
+        };
+        int[] colors = {
+            Color.WHITE,
+            Color.BLUE
+        };
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    Paint paint = new Paint();
+                    paint.setColor(Color.WHITE);
+                    canvas.drawRect(0, 0, width, height, paint);
+
+                    // These are exclusive, but if the replace op properly
+                    // removes the earlier intersect, only the right portion of
+                    // the canvas will be filled with blue.
+                    canvas.clipRect(0, 0, 5, height);
+                    canvas.clipRect(5, 0, width, height, Region.Op.REPLACE);
+
+                    paint.setColor(Color.BLUE);
+                    canvas.drawRect(0, 0, width, height, paint);
+                })
+                .runWithVerifier(new SamplePointVerifier(testPoints, colors));
+    }
+
+    @Test
+    public void testClipUnsupportedExpandingOps() {
+        // Test that other expanding clip ops are silently ignored for v27
+        createTest()
+                .addCanvasClient((canvas, width, height) -> {
+                    // Should not modify clip state so later draw fills bitmap
+                    canvas.clipRect(0, 0, 5, 5, Region.Op.REVERSE_DIFFERENCE);
+                    canvas.clipRect(5, 5, 10, 10, Region.Op.XOR);
+                    canvas.clipRect(10, 10, 15, 15, Region.Op.UNION);
+
+                    Paint paint = new Paint();
+                    paint.setColor(Color.RED);
+                    canvas.drawRect(0, 0, width, height, paint);
+                })
+                .runWithVerifier(new ColorVerifier(Color.RED));
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index b70166b..377182f 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -46,6 +46,7 @@
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Set;
+import java.util.function.BiFunction;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
@@ -635,6 +636,20 @@
         }
     }
 
+    @Test
+    public void testForEach() {
+        ArrayMap<String, Integer> map = new ArrayMap<>();
+
+        for (int i = 0; i < 50; ++i) {
+            map.put(Integer.toString(i), i * 10);
+        }
+
+        // Make sure forEach goes through all of the elements.
+        HashMap<String, Integer> seen = new HashMap<>();
+        map.forEach(seen::put);
+        compareMaps(seen, map);
+    }
+
     /**
      * The entrySet Iterator returns itself from each call to {@code next()}.
      * This is unusual behavior for {@link Iterator#next()}; this test ensures that
@@ -699,4 +714,20 @@
             fail("ArrayMap removeAll failure, expect empty, but " + map);
         }
     }
+
+    @Test
+    public void testReplaceAll() {
+        final ArrayMap<Integer, Integer> map = new ArrayMap<>();
+        final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
+        final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
+        for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
+            map.put(i, i);
+            expectedMap.put(i, 2 * i);
+        }
+
+        map.replaceAll(function);
+        if (!compare(map, expectedMap)) {
+            fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
+        }
+    }
 }
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
index 70d307a..42309e3 100644
--- a/tests/tests/util/src/android/util/cts/ArraySetTest.java
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -412,6 +412,20 @@
     }
 
     @Test
+    public void testForEach() {
+        ArraySet<Integer> set = new ArraySet<>();
+
+        for (int i = 0; i < 50; ++i) {
+            set.add(i * 10);
+        }
+
+        // Make sure forEach goes through all of the elements.
+        HashSet<Integer> seen = new HashSet<>();
+        set.forEach(seen::add);
+        compareSets(seen, set);
+    }
+
+    @Test
     public void testIndexOf() {
         ArraySet<Integer> set = new ArraySet<>();
 
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index a892da1..17e8889 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -411,7 +411,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name="android.view.cts.InputDeviceKeyLayoutMapTestActivity"
+        <activity android:name="android.view.cts.input.InputDeviceKeyLayoutMapTestActivity"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
@@ -461,6 +461,13 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.view.cts.InputQueueCtsActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+            </intent-filter>
+        </activity>
+
         <!-- Overrides the activity declaration in AndroidX test library to remove the starting
              animation. -->
         <activity
@@ -468,6 +475,14 @@
             tools:replace="android:theme"
             android:theme="@style/WhiteBackgroundTheme" />
 
+        <activity android:name="android.view.cts.OnBackInvokedDispatcherTestActivity"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"/>
+            </intent-filter>
+        </activity>
+
         <service android:name="android.view.textclassifier.cts.CtsTextClassifierService"
              android:exported="true"
              android:permission="android.permission.BIND_TEXTCLASSIFIER_SERVICE">
diff --git a/tests/tests/view/OWNERS b/tests/tests/view/OWNERS
index 7074017..0ece2bb 100644
--- a/tests/tests/view/OWNERS
+++ b/tests/tests/view/OWNERS
@@ -1,6 +1,6 @@
 # Bug component: 25700
 adamp@google.com
-shepshapard@google.com
 clarabayarri@google.com
 jreck@google.com
-njawad@google.com
\ No newline at end of file
+njawad@google.com
+svv@google.com
diff --git a/tests/tests/view/jni/Android.bp b/tests/tests/view/jni/Android.bp
index 107650e..de254ed 100644
--- a/tests/tests/view/jni/Android.bp
+++ b/tests/tests/view/jni/Android.bp
@@ -33,6 +33,7 @@
         "android_view_cts_ASurfaceControlTest.cpp",
         "android_view_cts_ChoreographerNativeTest.cpp",
         "android_view_cts_InputDeviceKeyLayoutMapTest.cpp",
+        "android_view_cts_InputQueueTest.cpp",
     ],
 
     shared_libs: [
diff --git a/tests/tests/view/jni/ChoreographerTestUtils.h b/tests/tests/view/jni/ChoreographerTestUtils.h
new file mode 100644
index 0000000..0a66f28
--- /dev/null
+++ b/tests/tests/view/jni/ChoreographerTestUtils.h
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ *
+ */
+
+#include <android/choreographer.h>
+#include <android/log.h>
+#include <android/looper.h>
+#include <android/trace.h>
+#include <jni.h>
+#include <jniAssert.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <chrono>
+#include <cinttypes>
+#include <cmath>
+#include <cstdlib>
+#include <cstring>
+#include <limits>
+#include <mutex>
+#include <set>
+#include <sstream>
+#include <string>
+#include <thread>
+#include <tuple>
+#include <vector>
+
+using namespace std::chrono_literals;
+static constexpr std::chrono::nanoseconds NOMINAL_VSYNC_PERIOD{16ms};
+static constexpr std::chrono::nanoseconds DELAY_PERIOD{NOMINAL_VSYNC_PERIOD * 5};
+static constexpr std::chrono::nanoseconds ZERO{std::chrono::nanoseconds::zero()};
+
+#define NANOS_PER_SECOND 1000000000LL
+static int64_t systemTime() {
+    struct timespec time;
+    int result = clock_gettime(CLOCK_MONOTONIC, &time);
+    if (result < 0) {
+        return -errno;
+    }
+    return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
+}
+
+static std::mutex gLock;
+struct Callback {
+    Callback(const char* name) : name(name) {}
+    std::string name;
+    int count{0};
+    std::chrono::nanoseconds frameTime{0LL};
+};
+
+struct ExtendedCallback : Callback {
+    ExtendedCallback(const char* name, JNIEnv* env) : Callback(name), env(env) {}
+
+    struct FrameTime {
+        FrameTime(const AChoreographerFrameCallbackData* callbackData, int index)
+              : vsyncId(AChoreographerFrameCallbackData_getFrameTimelineVsyncId(callbackData,
+                                                                                index)),
+                expectedPresentTime(
+                        AChoreographerFrameCallbackData_getFrameTimelineExpectedPresentTimeNanos(
+                                callbackData, index)),
+                deadline(AChoreographerFrameCallbackData_getFrameTimelineDeadlineNanos(callbackData,
+                                                                                       index)) {}
+
+        const int64_t vsyncId{-1};
+        const int64_t expectedPresentTime{-1};
+        const int64_t deadline{-1};
+    };
+
+    void populate(const AChoreographerFrameCallbackData* callbackData) {
+        size_t index = AChoreographerFrameCallbackData_getPreferredFrameTimelineIndex(callbackData);
+        preferredFrameTimelineIndex = index;
+
+        size_t length = AChoreographerFrameCallbackData_getFrameTimelinesLength(callbackData);
+        {
+            std::lock_guard<std::mutex> _l{gLock};
+            ASSERT(length >= 1, "Frame timelines should not be empty");
+            ASSERT(index < length, "Frame timeline index must be less than length");
+        }
+        timeline.reserve(length);
+
+        for (int i = 0; i < length; i++) {
+            timeline.push_back(FrameTime(callbackData, i));
+        }
+    }
+
+    size_t getPreferredFrameTimelineIndex() const { return preferredFrameTimelineIndex; }
+    const std::vector<FrameTime>& getTimeline() const { return timeline; }
+
+private:
+    JNIEnv* env;
+    size_t preferredFrameTimelineIndex{std::numeric_limits<size_t>::max()};
+    std::vector<FrameTime> timeline;
+};
+
+static void extendedFrameCallback(int64_t frameTimeNanos, void* data) {
+    std::lock_guard<std::mutex> _l(gLock);
+    ATrace_beginSection("extendedFrameCallback base");
+    Callback* cb = static_cast<Callback*>(data);
+    cb->count++;
+    cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
+    ATrace_endSection();
+}
+
+static void extendedFrameCallback(const AChoreographerFrameCallbackData* callbackData, void* data) {
+    ATrace_beginSection("extendedFrameCallback");
+    extendedFrameCallback(AChoreographerFrameCallbackData_getFrameTimeNanos(callbackData), data);
+
+    ExtendedCallback* cb = static_cast<ExtendedCallback*>(data);
+    cb->populate(callbackData);
+    ATrace_endSection();
+}
+
+static void frameCallback64(int64_t frameTimeNanos, void* data) {
+    extendedFrameCallback(frameTimeNanos, data);
+}
+
+static void frameCallback(long frameTimeNanos, void* data) {
+    extendedFrameCallback((int64_t)frameTimeNanos, data);
+}
+
+static std::chrono::nanoseconds now() {
+    return std::chrono::steady_clock::now().time_since_epoch();
+}
+
+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.c_str(), expectedCount, cb.count);
+    if (maxTime > ZERO) {
+        auto duration = cb.frameTime - startTime;
+        ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
+               cb.name.c_str(), expectedCount);
+    }
+}
diff --git a/tests/tests/view/jni/CtsViewJniOnLoad.cpp b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
index 2d4b3d7..92d57b4 100644
--- a/tests/tests/view/jni/CtsViewJniOnLoad.cpp
+++ b/tests/tests/view/jni/CtsViewJniOnLoad.cpp
@@ -23,6 +23,7 @@
 extern int register_android_view_cts_AKeyEventNativeTest(JNIEnv *env);
 extern int register_android_view_cts_AMotionEventNativeTest(JNIEnv *env);
 extern int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv *env);
+extern int register_android_view_cts_InputQueueTest(JNIEnv *env);
 
 jint JNI_OnLoad(JavaVM *vm, void *) {
     JNIEnv *env = NULL;
@@ -44,5 +45,8 @@
     if (register_android_view_cts_InputDeviceKeyLayoutMapTest(env)) {
         return JNI_ERR;
     }
+    if (register_android_view_cts_InputQueueTest(env)) {
+        return JNI_ERR;
+    }
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
index f315fbc..95f61c9 100644
--- a/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ASurfaceControlTest.cpp
@@ -16,12 +16,17 @@
 
 #define LOG_TAG "ASurfaceControlTest"
 
+#include <ChoreographerTestUtils.h>
+#include <android/choreographer.h>
 #include <android/data_space.h>
 #include <android/hardware_buffer.h>
+#include <android/hardware_buffer_jni.h>
 #include <android/log.h>
+#include <android/looper.h>
 #include <android/native_window_jni.h>
 #include <android/surface_control.h>
 #include <android/sync.h>
+#include <android/trace.h>
 #include <errno.h>
 #include <jni.h>
 #include <poll.h>
@@ -43,16 +48,6 @@
     jmethodID onTransactionComplete;
 } gTransactionCompleteListenerClassInfo;
 
-#define NANOS_PER_SECOND 1000000000LL
-int64_t systemTime() {
-    struct timespec time;
-    int result = clock_gettime(CLOCK_MONOTONIC, &time);
-    if (result < 0) {
-        return -errno;
-    }
-    return (time.tv_sec * NANOS_PER_SECOND) + time.tv_nsec;
-}
-
 static AHardwareBuffer* allocateBuffer(int32_t width, int32_t height) {
     AHardwareBuffer* buffer = nullptr;
     AHardwareBuffer_Desc desc = {};
@@ -98,6 +93,7 @@
     AHardwareBuffer_lock(buffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN, -1, &rect,
                                              &data);
     if (!data) {
+        AHardwareBuffer_release(buffer);
         return true;
     }
 
@@ -109,6 +105,16 @@
     return false;
 }
 
+jobject Utils_getSolidBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height, jint color) {
+    AHardwareBuffer* buffer;
+    if (getSolidBuffer(width, height, static_cast<uint32_t>(color), &buffer, nullptr)) {
+        return nullptr;
+    }
+    jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer);
+    AHardwareBuffer_release(buffer);
+    return result;
+}
+
 static bool getQuadrantBuffer(int32_t width, int32_t height, jint colorTopLeft,
                               jint colorTopRight, jint colorBottomRight,
                               jint colorBottomLeft,
@@ -143,6 +149,19 @@
     return false;
 }
 
+jobject Utils_getQuadrantBuffer(JNIEnv* env, jobject /*clazz*/, jint width, jint height,
+                                jint colorTopLeft, jint colorTopRight, jint colorBottomRight,
+                                jint colorBottomLeft) {
+    AHardwareBuffer* buffer;
+    if (getQuadrantBuffer(width, height, colorTopLeft, colorTopRight, colorBottomRight,
+                          colorBottomLeft, &buffer, nullptr)) {
+        return nullptr;
+    }
+    jobject result = AHardwareBuffer_toHardwareBuffer(env, buffer);
+    AHardwareBuffer_release(buffer);
+    return result;
+}
+
 jlong SurfaceTransaction_create(JNIEnv* /*env*/, jclass) {
     return reinterpret_cast<jlong>(ASurfaceTransaction_create());
 }
@@ -544,6 +563,77 @@
                                     nullptr, transactionCallbackWithoutContextThunk);
 }
 
+void SurfaceTransaction_setFrameTimeline(JNIEnv* /*env*/, jclass, jlong surfaceTransaction,
+                                         jlong vsyncId) {
+    ASurfaceTransaction_setFrameTimeline(reinterpret_cast<ASurfaceTransaction*>(surfaceTransaction),
+                                         vsyncId);
+}
+
+static struct {
+    jclass clazz;
+    jmethodID constructor;
+} gFrameTimelineClassInfo;
+
+static struct {
+    jclass clazz;
+    jmethodID constructor;
+} gFrameCallbackDataClassInfo;
+
+static void verifyChoreographer(JNIEnv* env, AChoreographer* choreographer) {
+    ASSERT(choreographer != nullptr, "Choreographer setup unsuccessful");
+}
+
+static void verifyPollCallback(JNIEnv* env, int result) {
+    ASSERT(result == ALOOPER_POLL_CALLBACK, "Callback failed with error: %d", result);
+}
+
+/** Gets VSync information from Choreographer, including a collection of frame timelines and
+ * platform-preferred index using Choreographer. */
+jobject SurfaceControlTest_getFrameTimelines(JNIEnv* env, jclass) {
+    ALooper_prepare(0);
+    ATrace_beginSection("Getting Choreographer instance");
+    AChoreographer* choreographer = AChoreographer_getInstance();
+    ATrace_endSection();
+    verifyChoreographer(env, choreographer);
+
+    ExtendedCallback cb1("cb1", env);
+    auto start = now();
+    ATrace_beginSection("postExtendedFrameCallback");
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    ATrace_endSection();
+    auto delayPeriod = std::chrono::duration_cast<std::chrono::milliseconds>(DELAY_PERIOD).count();
+    ATrace_beginSection("ALooper_pollOnce");
+    int result = ALooper_pollOnce(delayPeriod * 5, nullptr, nullptr, nullptr);
+    ATrace_endSection();
+    verifyPollCallback(env, result);
+    verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+
+    jobjectArray frameTimelineObjs =
+            env->NewObjectArray(cb1.getTimeline().size(), gFrameTimelineClassInfo.clazz,
+                                /*initial element*/ NULL);
+    if (env->ExceptionCheck()) {
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+        return NULL;
+    }
+    if (frameTimelineObjs == NULL) {
+        jniThrowRuntimeException(env, "Failed to create FrameTimeline array");
+        return NULL;
+    }
+    for (int i = 0; i < cb1.getTimeline().size(); i++) {
+        ExtendedCallback::FrameTime frameTimeline = cb1.getTimeline()[i];
+        jobject frameTimelineObj =
+                env->NewObject(gFrameTimelineClassInfo.clazz, gFrameTimelineClassInfo.constructor,
+                               frameTimeline.vsyncId, frameTimeline.expectedPresentTime,
+                               frameTimeline.deadline);
+        env->SetObjectArrayElement(frameTimelineObjs, i, frameTimelineObj);
+    }
+
+    return env->NewObject(gFrameCallbackDataClassInfo.clazz,
+                          gFrameCallbackDataClassInfo.constructor, frameTimelineObjs,
+                          cb1.getPreferredFrameTimelineIndex());
+}
+
 static const JNINativeMethod JNI_METHODS[] = {
         {"nSurfaceTransaction_create", "()J", (void*)SurfaceTransaction_create},
         {"nSurfaceTransaction_delete", "(J)V", (void*)SurfaceTransaction_delete},
@@ -591,6 +681,16 @@
         {"nSurfaceTransaction_setOnCommitCallbackWithoutContext",
          "(JLandroid/view/cts/util/ASurfaceControlTestUtils$TransactionCompleteListener;)V",
          (void*)SurfaceTransaction_setOnCommitCallbackWithoutContext},
+        {"nSurfaceTransaction_setFrameTimeline", "(JJ)V",
+         (void*)SurfaceTransaction_setFrameTimeline},
+        {"getSolidBuffer", "(III)Landroid/hardware/HardwareBuffer;", (void*)Utils_getSolidBuffer},
+        {"getQuadrantBuffer", "(IIIIII)Landroid/hardware/HardwareBuffer;",
+         (void*)Utils_getQuadrantBuffer},
+};
+
+static const JNINativeMethod FRAME_TIMELINE_JNI_METHODS[] = {
+        {"nGetFrameTimelines", "()Landroid/view/cts/util/FrameCallbackData;",
+         (void*)SurfaceControlTest_getFrameTimelines},
 };
 
 }  // anonymous namespace
@@ -602,6 +702,22 @@
             static_cast<jclass>(env->NewGlobalRef(transactionCompleteListenerClazz));
     gTransactionCompleteListenerClassInfo.onTransactionComplete =
             env->GetMethodID(transactionCompleteListenerClazz, "onTransactionComplete", "(JJ)V");
+
+    gFrameTimelineClassInfo.clazz = static_cast<jclass>(env->NewGlobalRef(
+            env->FindClass("android/view/cts/util/FrameCallbackData$FrameTimeline")));
+    gFrameTimelineClassInfo.constructor =
+            env->GetMethodID(gFrameTimelineClassInfo.clazz, "<init>", "(JJJ)V");
+
+    gFrameCallbackDataClassInfo.clazz = static_cast<jclass>(
+            env->NewGlobalRef(env->FindClass("android/view/cts/util/FrameCallbackData")));
+    gFrameCallbackDataClassInfo.constructor =
+            env->GetMethodID(gFrameCallbackDataClassInfo.clazz, "<init>",
+                             "([Landroid/view/cts/util/FrameCallbackData$FrameTimeline;I)V");
+
+    jclass frameCallbackDataClass = env->FindClass("android/view/cts/util/FrameCallbackData");
+    env->RegisterNatives(frameCallbackDataClass, FRAME_TIMELINE_JNI_METHODS,
+                         sizeof(FRAME_TIMELINE_JNI_METHODS) / sizeof(JNINativeMethod));
+
     jclass clazz = env->FindClass("android/view/cts/util/ASurfaceControlTestUtils");
     return env->RegisterNatives(clazz, JNI_METHODS, sizeof(JNI_METHODS) / sizeof(JNINativeMethod));
 }
diff --git a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
index 6206418..a7443ed 100644
--- a/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_ChoreographerNativeTest.cpp
@@ -15,6 +15,7 @@
  *
  */
 
+#include <ChoreographerTestUtils.h>
 #include <android/choreographer.h>
 #include <android/looper.h>
 #include <jni.h>
@@ -25,27 +26,20 @@
 #include <cmath>
 #include <cstdlib>
 #include <cstring>
+#include <limits>
 #include <mutex>
 #include <set>
 #include <sstream>
 #include <string>
 #include <thread>
+#include <tuple>
+#include <vector>
 
 #define LOG_TAG "ChoreographerNativeTest"
 
-#define ASSERT(condition, format, args...) \
-        if (!(condition)) { \
-            fail(env, format, ## args); \
-            return; \
-        }
-
 
 using namespace std::chrono_literals;
 
-static constexpr std::chrono::nanoseconds NOMINAL_VSYNC_PERIOD{16ms};
-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;
@@ -53,14 +47,7 @@
     } choreographerNativeTest;
 } gJni;
 
-static std::mutex gLock;
 static std::set<int64_t> gSupportedRefreshPeriods;
-struct Callback {
-    Callback(const char* name): name(name) {}
-    std::string name;
-    int count{0};
-    std::chrono::nanoseconds frameTime{0LL};
-};
 
 struct RefreshRateCallback {
     RefreshRateCallback(const char* name): name(name) {}
@@ -79,17 +66,6 @@
     std::chrono::nanoseconds vsyncPeriod{0LL};
 };
 
-static void frameCallback64(int64_t frameTimeNanos, void* data) {
-    std::lock_guard<std::mutex> _l(gLock);
-    Callback* cb = static_cast<Callback*>(data);
-    cb->count++;
-    cb->frameTime = std::chrono::nanoseconds{frameTimeNanos};
-}
-
-static void frameCallback(long frameTimeNanos, void* data) {
-    frameCallback64((int64_t) frameTimeNanos, data);
-}
-
 static void refreshRateCallback(int64_t vsyncPeriodNanos, void* data) {
     std::lock_guard<std::mutex> _l(gLock);
     RefreshRateCallback* cb = static_cast<RefreshRateCallback*>(data);
@@ -108,37 +84,6 @@
                             static_cast<int>(std::round(1e9f / cb->vsyncPeriod.count())));
 }
 
-static std::chrono::nanoseconds now() {
-    return std::chrono::steady_clock::now().time_since_epoch();
-}
-
-static void fail(JNIEnv* env, const char* format, ...) {
-    va_list args;
-
-    va_start(args, format);
-    char *msg;
-    int rc = vasprintf(&msg, format, args);
-    va_end(args);
-
-    jclass exClass;
-    const char *className = "java/lang/AssertionError";
-    exClass = env->FindClass(className);
-    env->ThrowNew(exClass, msg);
-    free(msg);
-}
-
-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.c_str(), expectedCount, cb.count);
-    if (maxTime > ZERO) {
-        auto duration = cb.frameTime - startTime;
-        ASSERT(duration < maxTime, "Callback '%s' has incorrect frame time in invocation %d",
-               cb.name.c_str(), expectedCount);
-    }
-}
-
 static std::string dumpSupportedRefreshPeriods() {
     std::stringstream ss;
     ss << "{ ";
@@ -190,6 +135,101 @@
     return choreographer != nullptr;
 }
 
+static void
+android_view_cts_ChoreographerNativeTest_testPostExtendedCallbackWithoutDelayEventuallyRunsCallback(
+        JNIEnv* env, jclass, jlong choreographerPtr) {
+    AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+    ExtendedCallback cb1("cb1", env);
+    ExtendedCallback cb2("cb2", env);
+    auto start = now();
+
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &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;
+        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_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    start = now();
+    std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+    verifyCallback(env, cb1, 2, start, NOMINAL_VSYNC_PERIOD * 3);
+    verifyCallback(env, cb2, 1, start, ZERO);
+}
+
+static void android_view_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid(
+        JNIEnv* env, jclass, jlong choreographerPtr) {
+    AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+    ExtendedCallback cb1("cb1", env);
+    auto start = now();
+
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+    verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+    std::lock_guard<std::mutex> _l{gLock};
+    for (const ExtendedCallback::FrameTime& frameTime : cb1.getTimeline()) {
+        int64_t vsyncId = frameTime.vsyncId;
+        ASSERT(vsyncId >= 0, "Invalid vsync ID");
+        ASSERT(std::count_if(cb1.getTimeline().begin(), cb1.getTimeline().end(),
+                             [vsyncId](const ExtendedCallback::FrameTime& ft) {
+                                 return ft.vsyncId == vsyncId;
+                             }) == 1,
+               "Vsync ID is not unique");
+    }
+}
+
+static void android_view_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture(
+        JNIEnv* env, jclass, jlong choreographerPtr) {
+    AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+    ExtendedCallback cb1("cb1", env);
+    auto start = now();
+
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+    verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+    std::lock_guard<std::mutex> _l{gLock};
+    for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < cb1.getTimeline().size(); i++) {
+        auto deadline = std::chrono::nanoseconds{cb1.getTimeline()[i].deadline};
+        ASSERT(deadline > std::chrono::nanoseconds{start}, "Deadline must be after start time");
+        ASSERT(deadline > cb1.frameTime, "Deadline must be after frame time");
+        ASSERT(deadline > lastValue, "Deadline must be greater than last frame deadline");
+        lastValue = deadline;
+    }
+}
+
+static void
+android_view_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture(
+        JNIEnv* env, jclass, jlong choreographerPtr) {
+    AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
+    ExtendedCallback cb1("cb1", env);
+    auto start = now();
+
+    AChoreographer_postExtendedFrameCallback(choreographer, extendedFrameCallback, &cb1);
+    std::this_thread::sleep_for(NOMINAL_VSYNC_PERIOD * 3);
+
+    verifyCallback(env, cb1, 1, start, NOMINAL_VSYNC_PERIOD * 3);
+    std::lock_guard<std::mutex> _l{gLock};
+    for (auto [i, lastValue] = std::tuple{0, cb1.frameTime}; i < cb1.getTimeline().size(); i++) {
+        auto expectedPresentTime =
+                std::chrono::nanoseconds(cb1.getTimeline()[i].expectedPresentTime);
+        auto deadline = std::chrono::nanoseconds(cb1.getTimeline()[i].deadline);
+        ASSERT(expectedPresentTime > cb1.frameTime,
+               "Expected present time must be after frame time");
+        ASSERT(expectedPresentTime > deadline, "Expected present time must be after deadline");
+        ASSERT(expectedPresentTime > lastValue,
+               "Expected present time must be greater than last frame expected present time");
+        lastValue = expectedPresentTime;
+    }
+}
+
 static void android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback(
         JNIEnv* env, jclass, jlong choreographerPtr) {
     AChoreographer* choreographer = reinterpret_cast<AChoreographer*>(choreographerPtr);
@@ -510,6 +550,14 @@
          (void*)android_view_cts_ChoreographerNativeTest_getChoreographer},
         {"nativePrepareChoreographerTests", "(J[J)Z",
          (void*)android_view_cts_ChoreographerNativeTest_prepareChoreographerTests},
+        {"nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testPostExtendedCallbackWithoutDelayEventuallyRunsCallback},
+        {"nativeTestFrameCallbackDataVsyncIdValid", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataVsyncIdValid},
+        {"nativeTestFrameCallbackDataDeadlineInFuture", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataDeadlineInFuture},
+        {"nativeTestFrameCallbackDataExpectedPresentTimeInFuture", "(J)V",
+         (void*)android_view_cts_ChoreographerNativeTest_testFrameCallbackDataExpectedPresentTimeInFuture},
         {"nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks", "(J)V",
          (void*)android_view_cts_ChoreographerNativeTest_testPostCallback64WithoutDelayEventuallyRunsCallback},
         {"nativeTestPostCallback64WithDelayEventuallyRunsCallbacks", "(J)V",
diff --git a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
index 4d2c810..8f25d39 100644
--- a/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
+++ b/tests/tests/view/jni/android_view_cts_InputDeviceKeyLayoutMapTest.cpp
@@ -107,6 +107,6 @@
 };
 
 int register_android_view_cts_InputDeviceKeyLayoutMapTest(JNIEnv* env) {
-    jclass clazz = env->FindClass("android/view/cts/InputDeviceKeyLayoutMapTest");
+    jclass clazz = env->FindClass("android/view/cts/input/InputDeviceKeyLayoutMapTest");
     return env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(JNINativeMethod));
 }
diff --git a/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp b/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp
new file mode 100644
index 0000000..769c95b
--- /dev/null
+++ b/tests/tests/view/jni/android_view_cts_InputQueueTest.cpp
@@ -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.
+ *
+ */
+
+#include <android/input.h>
+#include <jni.h>
+#include <jniAssert.h>
+
+#include <array>
+#include <thread>
+
+#define LOG_TAG "InputQueueTest"
+
+bool waitForEvent(JNIEnv *env, jclass /* clazz */, jobject inputQueue) {
+    constexpr size_t NUM_TRIES = 5;
+    for (size_t i = 0; i < NUM_TRIES; i++) {
+        AInputQueue *nativeQueue = AInputQueue_fromJava(env, inputQueue);
+        if (nativeQueue != nullptr) {
+            int32_t numEvents = AInputQueue_hasEvents(nativeQueue);
+            if (numEvents > 0) {
+                return true;
+            }
+        }
+        std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    }
+    return false;
+}
+
+void inputQueueTest(JNIEnv *env, jclass /* clazz */, jobject inputQueue) {
+    AInputQueue *nativeQueue = AInputQueue_fromJava(env, inputQueue);
+    ASSERT(nativeQueue != nullptr, "Native input queue not returned");
+    AInputEvent *event = nullptr;
+    ASSERT(AInputQueue_getEvent(nativeQueue, &event) >= 0, "getEvent did not succeed");
+    ASSERT(AInputEvent_getType(event) == AINPUT_EVENT_TYPE_MOTION, "Wrong event type");
+    ASSERT(AKeyEvent_getAction(event) == AKEY_EVENT_ACTION_DOWN, "Wrong action");
+    AInputQueue_finishEvent(nativeQueue, event, true);
+}
+
+const std::array<JNINativeMethod, 2> JNI_METHODS = {{
+        {"waitForEvent", "(Landroid/view/InputQueue;)Z", (void *)waitForEvent},
+        {"inputQueueTest", "(Landroid/view/InputQueue;)V", (void *)inputQueueTest},
+}};
+
+jint register_android_view_cts_InputQueueTest(JNIEnv *env) {
+    jclass clazzTest = env->FindClass("android/view/cts/InputQueueTest");
+    return env->RegisterNatives(clazzTest, JNI_METHODS.data(), JNI_METHODS.size());
+}
diff --git a/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml b/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml
new file mode 100644
index 0000000..b8c365a
--- /dev/null
+++ b/tests/tests/view/res/layout/onbackinvokeddispatcher_dialog_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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">
+
+    <View
+        android:id="@+id/test_view_in_dialog"
+        android:layout_width="100px"
+        android:layout_height="100px"
+        android:layout_centerInParent="true"
+        android:background="#FFF"/>
+
+</RelativeLayout>
diff --git a/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml b/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml
new file mode 100644
index 0000000..40ff58b
--- /dev/null
+++ b/tests/tests/view/res/layout/onbackinvokeddispatcher_layout.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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">
+
+    <View
+        android:id="@+id/test_view"
+        android:layout_width="100px"
+        android:layout_height="100px"
+        android:layout_centerInParent="true"
+        android:background="#FFF"/>
+
+</RelativeLayout>
diff --git a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
index 1b120f9..72b4e97 100644
--- a/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
+++ b/tests/tests/view/src/android/view/cts/ASurfaceControlTest.java
@@ -18,6 +18,7 @@
 
 import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
 import static android.view.cts.surfacevalidator.ASurfaceControlTestActivity.MultiRectChecker;
+import static android.view.cts.surfacevalidator.ASurfaceControlTestActivity.WAIT_TIMEOUT_S;
 import static android.view.cts.util.ASurfaceControlTestUtils.applyAndDeleteSurfaceTransaction;
 import static android.view.cts.util.ASurfaceControlTestUtils.createSurfaceTransaction;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceControl_acquire;
@@ -30,6 +31,7 @@
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_releaseBuffer;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setDamageRegion;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setDesiredPresentTime;
+import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setFrameTimeline;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCommitCallback;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCommitCallbackWithoutContext;
 import static android.view.cts.util.ASurfaceControlTestUtils.nSurfaceTransaction_setOnCompleteCallback;
@@ -48,14 +50,17 @@
 import static android.view.cts.util.ASurfaceControlTestUtils.setScale;
 import static android.view.cts.util.ASurfaceControlTestUtils.setVisibility;
 import static android.view.cts.util.ASurfaceControlTestUtils.setZOrder;
+import static android.view.cts.util.FrameCallbackData.nGetFrameTimelines;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.platform.test.annotations.RequiresDevice;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
@@ -65,11 +70,14 @@
 import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.PixelChecker;
 import android.view.cts.surfacevalidator.PixelColor;
 import android.view.cts.util.ASurfaceControlTestUtils;
+import android.view.cts.util.FrameCallbackData;
+import android.view.cts.util.FrameCallbackData.FrameTimeline;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.rules.ActivityScenarioRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -238,7 +246,7 @@
 
     private void verifyTest(BasicSurfaceHolderCallback callback, PixelChecker pixelChecker) {
         SurfaceHolderCallback surfaceHolderCallback = new SurfaceHolderCallback(callback);
-        mActivity.verifyTest(surfaceHolderCallback, pixelChecker);
+        mActivity.verifyTest(surfaceHolderCallback, pixelChecker, mName);
     }
 
     @Test
@@ -447,7 +455,7 @@
                         long surfaceControl = createFromWindow(holder.getSurface());
 
                         setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
-                                PixelColor.RED);
+                                PixelColor.TRANSLUCENT_RED);
                         setBufferOpaque(surfaceControl, true);
                     }
                 },
@@ -460,7 +468,7 @@
     }
 
     @Test
-    public void testSurfaceTransaction_setBufferOpaque_transparent() {
+    public void testSurfaceTransaction_setBufferOpaque_translucent() {
         verifyTest(
                 new BasicSurfaceHolderCallback() {
                     @Override
@@ -468,7 +476,7 @@
                         long surfaceControl = createFromWindow(holder.getSurface());
 
                         setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
-                                PixelColor.TRANSPARENT_RED);
+                                PixelColor.TRANSLUCENT_RED);
                         setBufferOpaque(surfaceControl, false);
                     }
                 },
@@ -669,7 +677,6 @@
                         setGeometry(surfaceControl2, 0, 0, 100, 100, 70, 20, 90, 50, 0);
                     }
                 },
-
                 new MultiRectChecker(DEFAULT_RECT) {
                     @Override
                     public PixelColor getExpectedColor(int x, int y) {
@@ -697,7 +704,6 @@
                                 PixelColor.MAGENTA, PixelColor.GREEN);
                     }
                 },
-
                 new MultiRectChecker(DEFAULT_RECT) {
                     @Override
                     public PixelColor getExpectedColor(int x, int y) {
@@ -1619,6 +1625,50 @@
     }
 
     @Test
+    public void testSurfaceTransaction_setPositionAndScale() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        long surfaceControl = createFromWindow(holder.getSurface());
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+
+                        // Set the position to -50, -50 in parent space then scale 2x in each
+                        // direction relative to 0,0. The end result should be a -50,-50,150,150
+                        // buffer coverage or essentially a 2x center-scale
+
+                        setPosition(surfaceControl, -50, -50);
+                        setScale(surfaceControl, 2, 2);
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return RED;
+                        } else if (x >= halfWidth && y < halfHeight) {
+                            return BLUE;
+                        } else if (x < halfWidth && y >= halfHeight) {
+                            return GREEN;
+                        } else {
+                            return MAGENTA;
+                        }
+                    }
+
+                    @Override
+                    public boolean checkPixels(int matchingPixelCount, int width, int height) {
+                        // There will be sampling artifacts along the center line, ignore those
+                        return matchingPixelCount > 9000 && matchingPixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
     public void testSurfaceTransaction_setBufferTransform90() {
         verifyTest(
                 new BasicSurfaceHolderCallback() {
@@ -1764,6 +1814,147 @@
                 });
     }
 
+    private void verifySetFrameTimeline(boolean mUsePreferredIndex, SurfaceHolder holder) {
+        TimedTransactionListener onCompleteCallback = new TimedTransactionListener();
+        long surfaceControl = nSurfaceControl_createFromWindow(holder.getSurface());
+        assertTrue("failed to create surface control", surfaceControl != 0);
+        long surfaceTransaction = createSurfaceTransaction();
+        long buffer = nSurfaceTransaction_setSolidBuffer(surfaceControl, surfaceTransaction,
+                DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+        assertTrue("failed to set buffer", buffer != 0);
+
+        // Get choreographer frame timelines.
+        FrameCallbackData frameCallbackData = nGetFrameTimelines();
+        FrameTimeline[] frameTimelines = frameCallbackData.getFrameTimelines();
+        assertTrue("Frame timelines length needs to be greater than 1", frameTimelines
+                .length > 1);
+        long interval = frameTimelines[1].getDeadline() - frameTimelines[0].getDeadline();
+
+        int timelineIndex = frameCallbackData.getPreferredFrameTimelineIndex();
+        if (!mUsePreferredIndex) {
+            assertNotEquals("Preferred frame timeline index should not be last index",
+                    frameTimelines.length - 1,
+                    frameCallbackData.getPreferredFrameTimelineIndex());
+            timelineIndex = frameTimelines.length - 1;
+        }
+        long vsyncId = frameTimelines[timelineIndex].getVsyncId();
+        Trace.beginSection("Surface transaction created " + vsyncId);
+        nSurfaceTransaction_setFrameTimeline(surfaceTransaction, vsyncId);
+        nSurfaceTransaction_setOnCompleteCallback(surfaceTransaction,
+                true /* waitForFence */, onCompleteCallback);
+        applyAndDeleteSurfaceTransaction(surfaceTransaction);
+        Trace.endSection();
+
+        Trace.beginSection("Wait for complete callback " + vsyncId);
+        // Wait for callbacks to fire.
+        try {
+            onCompleteCallback.mLatch.await(1, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+        }
+        if (onCompleteCallback.mLatch.getCount() > 0) {
+            Log.e(TAG, "Failed to wait for callback");
+        }
+        Trace.endSection();
+
+        assertEquals(0, onCompleteCallback.mLatch.getCount());
+        assertTrue(onCompleteCallback.mCallbackTime > 0);
+        assertTrue(onCompleteCallback.mLatchTime > 0);
+
+        FrameTimeline frameTimeline = frameTimelines[timelineIndex];
+        long lowerThreshold = frameTimeline.getExpectedPresentTime() - interval / 2;
+        assertTrue("Presented too early using frame timeline index=" + timelineIndex
+                        + " (preferred index="
+                        + frameCallbackData.getPreferredFrameTimelineIndex()
+                        + "), vsyncId=" + frameTimeline.getVsyncId() + ", presentTime="
+                        + onCompleteCallback.mPresentTime + ", expectedPresentTime="
+                        + frameTimeline.getExpectedPresentTime()
+                        + ", diff (ns)="
+                        + (frameTimeline.getExpectedPresentTime()
+                        - onCompleteCallback.mPresentTime),
+                frameTimeline.getExpectedPresentTime() >= lowerThreshold);
+        long upperThreshold = frameTimeline.getExpectedPresentTime() + interval / 2;
+        assertTrue("Present too late using frame timeline index=" + timelineIndex
+                        + " (preferred index="
+                        + frameCallbackData.getPreferredFrameTimelineIndex()
+                        + "), vsyncId=" + frameTimeline.getVsyncId() + ", presentTime="
+                        + onCompleteCallback.mPresentTime + ", expectedPresentTime="
+                        + frameTimeline.getExpectedPresentTime()
+                        + ", diff (ns)="
+                        + (frameTimeline.getExpectedPresentTime()
+                        - onCompleteCallback.mPresentTime),
+                frameTimeline.getExpectedPresentTime() < upperThreshold);
+    }
+
+    @Test
+    @RequiresDevice // emulators can't support sync fences
+    public void testSurfaceTransaction_setFrameTimeline_preferredIndex() {
+        Trace.beginSection(
+                "testSurfaceTransaction_setFrameTimeline_preferredIndex");
+        Trace.endSection();
+
+        BasicSurfaceHolderCallback basicSurfaceHolderCallback = new BasicSurfaceHolderCallback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder surfaceHolder) {
+                // Noop.
+            }
+        };
+        final CountDownLatch readyFence = new CountDownLatch(1);
+        ASurfaceControlTestActivity.SurfaceHolderCallback surfaceHolderCallback =
+                new ASurfaceControlTestActivity.SurfaceHolderCallback(
+                        new SurfaceHolderCallback(basicSurfaceHolderCallback), readyFence,
+                        mActivity.getParentFrameLayout().getViewTreeObserver());
+        mActivity.createSurface(surfaceHolderCallback);
+        try {
+            assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            Assert.fail("interrupted");
+        }
+        verifySetFrameTimeline(true, mActivity.getSurfaceView().getHolder());
+        mActivity.verifyScreenshot(
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                }, mName);
+
+    }
+
+    @Test
+    @RequiresDevice // emulators can't support sync fences
+    public void testSurfaceTransaction_setFrameTimeline_notPreferredIndex() {
+        Trace.beginSection(
+                "testSurfaceTransaction_setFrameTimeline_notPreferredIndex");
+        Trace.endSection();
+
+        BasicSurfaceHolderCallback basicSurfaceHolderCallback = new BasicSurfaceHolderCallback() {
+            @Override
+            public void surfaceCreated(SurfaceHolder surfaceHolder) {
+                // Noop.
+            }
+        };
+        final CountDownLatch readyFence = new CountDownLatch(1);
+        ASurfaceControlTestActivity.SurfaceHolderCallback surfaceHolderCallback =
+                new ASurfaceControlTestActivity.SurfaceHolderCallback(
+                        new SurfaceHolderCallback(basicSurfaceHolderCallback), readyFence,
+                        mActivity.getParentFrameLayout().getViewTreeObserver());
+        mActivity.createSurface(surfaceHolderCallback);
+        try {
+            assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            Assert.fail("interrupted");
+        }
+        verifySetFrameTimeline(false, mActivity.getSurfaceView().getHolder());
+        mActivity.verifyScreenshot(
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                }, mName);
+
+    }
+
     static class TimedTransactionListener implements
             ASurfaceControlTestUtils.TransactionCompleteListener {
         long mCallbackTime = -1;
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
index 96aa43b..261895d 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerNativeTest.java
@@ -75,6 +75,14 @@
     private static native void nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks(
             long ptr);
     private static native void nativeTestPostCallback64WithDelayEventuallyRunsCallbacks(long ptr);
+    private static native void nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks(
+            long ptr);
+    private static native void nativeTestFrameCallbackDataVsyncIdValid(
+            long ptr);
+    private static native void nativeTestFrameCallbackDataDeadlineInFuture(
+            long ptr);
+    private static native void nativeTestFrameCallbackDataExpectedPresentTimeInFuture(
+            long ptr);
     private static native void nativeTestPostCallbackMixedWithoutDelayEventuallyRunsCallbacks(
             long ptr);
     private static native void nativeTestPostCallbackMixedWithDelayEventuallyRunsCallbacks(
@@ -122,6 +130,30 @@
 
     @MediumTest
     @Test
+    public void testPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks() {
+        nativeTestPostExtendedCallbackWithoutDelayEventuallyRunsCallbacks(mChoreographerPtr);
+    }
+
+    @MediumTest
+    @Test
+    public void testFrameCallbackDataVsyncIdValid() {
+        nativeTestFrameCallbackDataVsyncIdValid(mChoreographerPtr);
+    }
+
+    @MediumTest
+    @Test
+    public void testFrameCallbackDataDeadlineInFuture() {
+        nativeTestFrameCallbackDataDeadlineInFuture(mChoreographerPtr);
+    }
+
+    @MediumTest
+    @Test
+    public void testFrameCallbackDataExpectedPresentTimeInFuture() {
+        nativeTestFrameCallbackDataExpectedPresentTimeInFuture(mChoreographerPtr);
+    }
+
+    @MediumTest
+    @Test
     public void testPostCallback64WithoutDelayEventuallyRunsCallbacks() {
         nativeTestPostCallback64WithoutDelayEventuallyRunsCallbacks(mChoreographerPtr);
     }
diff --git a/tests/tests/view/src/android/view/cts/ChoreographerTest.java b/tests/tests/view/src/android/view/cts/ChoreographerTest.java
index 3760b73..f6c9d4d 100644
--- a/tests/tests/view/src/android/view/cts/ChoreographerTest.java
+++ b/tests/tests/view/src/android/view/cts/ChoreographerTest.java
@@ -37,6 +37,8 @@
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 
+import java.util.HashSet;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ChoreographerTest {
@@ -290,4 +292,138 @@
     public void testRemoveNullFrameCallback() {
         mChoreographer.removeFrameCallback(null);
     }
+
+    @Test
+    public void testPostExtendedFrameCallbackWithoutDelay() {
+        final Choreographer.ExtendedFrameCallback addedCallback1 = mock(
+                Choreographer.ExtendedFrameCallback.class);
+        final Choreographer.ExtendedFrameCallback addedCallback2 = mock(
+                Choreographer.ExtendedFrameCallback.class);
+        final Choreographer.ExtendedFrameCallback removedCallback = mock(
+                Choreographer.ExtendedFrameCallback.class);
+
+        // Add and remove a few callbacks.
+        long postTimeNanos = System.nanoTime();
+        mChoreographer.postExtendedFrameCallback(addedCallback1);
+        mChoreographer.postExtendedFrameCallback(addedCallback2);
+        mChoreographer.postExtendedFrameCallback(removedCallback);
+        mChoreographer.removeExtendedFrameCallback(removedCallback);
+
+        // We expect the remaining callbacks to have been invoked once.
+        ArgumentCaptor<Choreographer.FrameData> captor1 = ArgumentCaptor.forClass(
+                Choreographer.FrameData.class);
+        ArgumentCaptor<Choreographer.FrameData> captor2 = ArgumentCaptor.forClass(
+                Choreographer.FrameData.class);
+        verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+                captor1.capture());
+        verify(addedCallback2, times(1)).onVsync(captor2.capture());
+        verifyZeroInteractions(removedCallback);
+        assertTimeDeltaLessThan(captor1.getValue().getFrameTimeNanos() - postTimeNanos,
+                NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+        assertTimeDeltaLessThan(captor2.getValue().getFrameTimeNanos() - postTimeNanos,
+                NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+
+        // If we post a callback again, then it should be invoked again.
+        postTimeNanos = System.nanoTime();
+        mChoreographer.postExtendedFrameCallback(addedCallback1);
+
+        verify(addedCallback1, timeout(NOMINAL_VSYNC_PERIOD * 10).times(2)).onVsync(
+                captor1.capture());
+        verify(addedCallback2, times(1)).onVsync(captor2.capture());
+        verifyZeroInteractions(removedCallback);
+        assertTimeDeltaLessThan(captor1.getValue().getFrameTimeNanos() - postTimeNanos,
+                NOMINAL_VSYNC_PERIOD * 10 * NANOS_PER_MS);
+        // Callback #2 is not invoked a second time so the time delta is not checked here.
+    }
+
+    @Test
+    public void testPostExtendedFrameCallbackFrameDataVsyncIdValid() {
+        final Choreographer.ExtendedFrameCallback addedCallback = mock(
+                Choreographer.ExtendedFrameCallback.class);
+        long postTimeNanos = System.nanoTime();
+        mChoreographer.postExtendedFrameCallback(addedCallback);
+
+        ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+                Choreographer.FrameData.class);
+        verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+                captor.capture());
+
+        Choreographer.FrameData frameData = captor.getValue();
+        assertTrue("Number of frame timelines should be greater than 0",
+                frameData.getFrameTimelines().length > 0);
+        HashSet<Long> pastVsyncIds = new HashSet();
+        for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+            long vsyncId = frameTimeline.getVsyncId();
+            assertTrue("Invalid vsync ID", vsyncId > 0);
+            assertTrue("Vsync ID should be unique", !pastVsyncIds.contains(vsyncId));
+            pastVsyncIds.add(vsyncId);
+        }
+    }
+
+    @Test
+    public void testPostExtendedFrameCallbackFrameDataDeadlineInFuture() {
+        final Choreographer.ExtendedFrameCallback addedCallback = mock(
+                Choreographer.ExtendedFrameCallback.class);
+        long postTimeNanos = System.nanoTime();
+        mChoreographer.postExtendedFrameCallback(addedCallback);
+
+        ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+                Choreographer.FrameData.class);
+        verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+                captor.capture());
+
+        Choreographer.FrameData frameData = captor.getValue();
+        assertTrue("Number of frame timelines should be greater than 0",
+                frameData.getFrameTimelines().length > 0);
+        long lastValue = frameData.getFrameTimeNanos();
+        for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+            long deadline = frameTimeline.getDeadlineNanos();
+            assertTrue("Deadline must be after start time", deadline > postTimeNanos);
+            assertTrue("Deadline must be after frame time",
+                    deadline > frameData.getFrameTimeNanos());
+            assertTrue("Deadline must be after the previous frame deadline",
+                    deadline > lastValue);
+            lastValue = deadline;
+        }
+    }
+
+    @Test
+    public void testPostExtendedFrameCallbackFrameDataExpectedPresentTimeInFuture() {
+        final Choreographer.ExtendedFrameCallback addedCallback = mock(
+                Choreographer.ExtendedFrameCallback.class);
+        long postTimeNanos = System.nanoTime();
+        mChoreographer.postExtendedFrameCallback(addedCallback);
+
+        ArgumentCaptor<Choreographer.FrameData> captor = ArgumentCaptor.forClass(
+                Choreographer.FrameData.class);
+        verify(addedCallback, timeout(NOMINAL_VSYNC_PERIOD * 10).times(1)).onVsync(
+                captor.capture());
+
+        Choreographer.FrameData frameData = captor.getValue();
+        assertTrue("Number of frame timelines should be greater than 0",
+                frameData.getFrameTimelines().length > 0);
+        long lastValue = frameData.getFrameTimeNanos();
+        for (Choreographer.FrameTimeline frameTimeline : frameData.getFrameTimelines()) {
+            long expectedPresentTime = frameTimeline.getExpectedPresentTimeNanos();
+            assertTrue("Expected present time must be after start time",
+                    expectedPresentTime > postTimeNanos);
+            assertTrue("Expected present time must be after frame time",
+                    expectedPresentTime > frameData.getFrameTimeNanos());
+            assertTrue(
+                    "Expected present time must be after the previous frame expected present "
+                            + "time",
+                    expectedPresentTime > lastValue);
+            lastValue = expectedPresentTime;
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testPostNullExtendedFrameCallback() {
+        mChoreographer.postExtendedFrameCallback(null);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRemoveNullExtendedFrameCallback() {
+        mChoreographer.removeExtendedFrameCallback(null);
+    }
 }
diff --git a/tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java b/tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java
similarity index 100%
rename from tests/tests/view/src/android/view/cts/input/InputEventInterceptTestActivity.java
rename to tests/tests/view/src/android/view/cts/InputEventInterceptTestActivity.java
diff --git a/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.java b/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.java
new file mode 100644
index 0000000..2705f53
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputQueueCtsActivity.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.view.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.InputQueue;
+
+public class InputQueueCtsActivity extends Activity implements InputQueue.Callback {
+    private InputQueue mInputQueue;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().takeInputQueue(this);
+    }
+
+    @Override
+    public void onInputQueueCreated(InputQueue inputQueue) {
+        mInputQueue = inputQueue;
+    }
+
+    @Override
+    public void onInputQueueDestroyed(InputQueue inputQueue) {}
+
+    public InputQueue getInputQueue() {
+        return mInputQueue;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/InputQueueTest.java b/tests/tests/view/src/android/view/cts/InputQueueTest.java
new file mode 100644
index 0000000..aefa38c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/InputQueueTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.view.cts;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.view.InputQueue;
+import android.view.MotionEvent;
+import android.view.Window;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link AInputQueue}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InputQueueTest {
+    private static final String LOG_TAG = InputQueueTest.class.getSimpleName();
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+    private static native boolean waitForEvent(InputQueue inputQueue);
+    private static native void inputQueueTest(InputQueue inputQueue);
+
+    @Rule
+    public ActivityTestRule<InputQueueCtsActivity> mTestActivityRule =
+            new ActivityTestRule<>(InputQueueCtsActivity.class);
+
+    @Test
+    public void testNativeInputQueue() throws Throwable {
+        InputQueueCtsActivity activity = mTestActivityRule.getActivity();
+        Window window = activity.getWindow();
+        InputQueue inputQueue = activity.getInputQueue();
+
+        // An event is created Java-side.
+        long now = SystemClock.uptimeMillis();
+        MotionEvent event = MotionEvent.obtain(now, now, MotionEvent.ACTION_DOWN, 0, 0, 0);
+        window.injectInputEvent(event);
+
+        assertTrue("Timed out waiting for event", waitForEvent(inputQueue));
+
+        inputQueueTest(inputQueue); // Check the injected event is received on the native side.
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/MotionEventTest.java b/tests/tests/view/src/android/view/cts/MotionEventTest.java
index 92e5832..bb8c20f 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventTest.java
@@ -169,10 +169,10 @@
     public void testObtainFromPropertyArrays() {
         PointerCoordsBuilder coordsBuilder0 =
                 withCoords(X_3F, Y_4F).withPressure(PRESSURE_1F).withSize(SIZE_1F).
-                        withTool(1.2f, 1.4f);
+                        withTool(1.2f, 1.4f).withGenericAxis1(2.6f);
         PointerCoordsBuilder coordsBuilder1 =
                 withCoords(X_3F + 1.0f, Y_4F - 2.0f).withPressure(PRESSURE_1F + 0.2f).
-                        withSize(SIZE_1F + 0.5f).withTouch(2.2f, 0.6f);
+                        withSize(SIZE_1F + 0.5f).withTouch(2.2f, 0.6f).withGenericAxis1(2.6f);
 
         PointerPropertiesBuilder propertiesBuilder0 =
                 withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -451,9 +451,11 @@
     @Test
     public void testGetCurrentDataWithTwoPointers() {
         PointerCoordsBuilder coordsBuilder0 =
-                withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f);
+                withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f,
+                        1.4f).withGenericAxis1(4.4f);
         PointerCoordsBuilder coordsBuilder1 =
-                withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f, 0.6f);
+                withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f,
+                        0.6f).withGenericAxis1(6.6f);
 
         PointerPropertiesBuilder propertiesBuilder0 =
                 withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -484,9 +486,11 @@
     @Test
     public void testGetRawCoordsWithTwoPointers() {
         PointerCoordsBuilder coordsBuilder0 =
-                withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f);
+                withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f,
+                        1.4f).withGenericAxis1(4.4f);
         PointerCoordsBuilder coordsBuilder1 =
-                withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f, 0.6f);
+                withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTouch(2.2f,
+                        0.6f).withGenericAxis1(6.6f);
 
         PointerPropertiesBuilder propertiesBuilder0 =
                 withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -518,10 +522,10 @@
         // PHASE 1 - construct the initial data for the event
         PointerCoordsBuilder coordsBuilderInitial0 =
                 withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
-                        withTouch(0.7f, 0.6f).withOrientation(2.0f);
+                        withTouch(0.7f, 0.6f).withOrientation(2.0f).withGenericAxis1(4.4f);
         PointerCoordsBuilder coordsBuilderInitial1 =
                 withCoords(30.0f, 40.0f).withPressure(1.4f).withSize(3.0f).withTool(1.3f, 1.7f).
-                        withTouch(2.7f, 3.6f).withOrientation(1.0f);
+                        withTouch(2.7f, 3.6f).withOrientation(1.0f).withGenericAxis1(5.4f);
 
         PointerPropertiesBuilder propertiesBuilder0 =
                 withProperties(0, MotionEvent.TOOL_TYPE_FINGER);
@@ -547,10 +551,10 @@
         // PHASE 2 - add a new batch of data to our event
         PointerCoordsBuilder coordsBuilderNext0 =
                 withCoords(15.0f, 25.0f).withPressure(1.6f).withSize(2.2f).withTool(1.2f, 1.4f).
-                        withTouch(1.0f, 0.9f).withOrientation(2.2f);
+                        withTouch(1.0f, 0.9f).withOrientation(2.2f).withGenericAxis1(7.4f);
         PointerCoordsBuilder coordsBuilderNext1 =
                 withCoords(35.0f, 45.0f).withPressure(1.8f).withSize(3.2f).withTool(1.2f, 1.4f).
-                        withTouch(0.7f, 0.6f).withOrientation(2.9f);
+                        withTouch(0.7f, 0.6f).withOrientation(2.9f).withGenericAxis1(8.4f);
 
         mMotionEventDynamic.addBatch(mEventTime + 10,
                 new PointerCoords[] { coordsBuilderNext0.build(), coordsBuilderNext1.build() }, 0);
@@ -576,10 +580,10 @@
         // PHASE 3 - add one more new batch of data to our event
         PointerCoordsBuilder coordsBuilderLast0 =
                 withCoords(18.0f, 28.0f).withPressure(1.1f).withSize(2.9f).withTool(1.5f, 1.9f).
-                        withTouch(1.2f, 5.0f).withOrientation(3.2f);
+                        withTouch(1.2f, 5.0f).withOrientation(3.1f).withGenericAxis1(1.4f);
         PointerCoordsBuilder coordsBuilderLast1 =
                 withCoords(38.0f, 48.0f).withPressure(1.2f).withSize(2.5f).withTool(0.2f, 0.4f).
-                        withTouch(2.7f, 4.6f).withOrientation(0.2f);
+                        withTouch(2.7f, 4.6f).withOrientation(0.2f).withGenericAxis1(5.4f);
 
         mMotionEventDynamic.addBatch(mEventTime + 20,
                 new PointerCoords[] { coordsBuilderLast0.build(), coordsBuilderLast1.build() }, 0);
@@ -676,7 +680,8 @@
             originalRawCoords[i] = new PointerCoords(c);
         }
         final MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE,
-                pointerCount, pointerIds, pointerCoords, 0, 0, 0, 0, 0, 0, 0);
+                pointerCount, pointerIds, pointerCoords, 0, 0, 0, 0, 0,
+                InputDevice.SOURCE_TOUCHSCREEN, 0);
         dump("Original points.", event);
 
         // Check original raw X and Y assumption.
diff --git a/tests/tests/view/src/android/view/cts/MotionEventUtils.java b/tests/tests/view/src/android/view/cts/MotionEventUtils.java
index 74291c7..3747a46 100644
--- a/tests/tests/view/src/android/view/cts/MotionEventUtils.java
+++ b/tests/tests/view/src/android/view/cts/MotionEventUtils.java
@@ -84,6 +84,7 @@
         private float toolMajor;
         private float toolMinor;
         private float orientation;
+        private float generic1;
 
         public PointerCoordsBuilder withPressure(float pressure) {
             this.pressure = pressure;
@@ -112,6 +113,11 @@
             return this;
         }
 
+        public PointerCoordsBuilder withGenericAxis1(float generic1) {
+            this.generic1 = generic1;
+            return this;
+        }
+
         public PointerCoords build() {
             final PointerCoords pointerCoords = new PointerCoords();
             pointerCoords.x = x;
@@ -123,6 +129,7 @@
             pointerCoords.toolMajor = toolMajor;
             pointerCoords.toolMinor = toolMinor;
             pointerCoords.orientation = orientation;
+            pointerCoords.setAxisValue(MotionEvent.AXIS_GENERIC_1, generic1);
             return pointerCoords;
         }
 
@@ -167,6 +174,9 @@
                     that.getOrientation(), this.orientation, DELTA);
             assertEquals("Orientation should be the same",
                     that.getAxisValue(MotionEvent.AXIS_ORIENTATION), this.orientation, DELTA);
+
+            assertEquals("Generic axis 1 should be the same",
+                    that.getAxisValue(MotionEvent.AXIS_GENERIC_1), this.generic1, DELTA);
         }
 
         public void verifyMatches(MotionEvent that, int pointerIndex) {
@@ -220,6 +230,10 @@
             assertEquals("Orientation should be the same",
                     that.getAxisValue(MotionEvent.AXIS_ORIENTATION, pointerIndex), this.orientation,
                         DELTA);
+
+            assertEquals("Generic axis 1 should be the same",
+                    that.getAxisValue(MotionEvent.AXIS_GENERIC_1, pointerIndex), this.generic1,
+                    DELTA);
         }
 
         public void verifyMatchesHistorical(MotionEvent that, int position) {
@@ -273,6 +287,10 @@
             assertEquals("Orientation should be the same",
                     that.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION, position),
                     this.orientation, DELTA);
+
+            assertEquals("Generic axis 1 should be the same",
+                    that.getHistoricalAxisValue(MotionEvent.AXIS_GENERIC_1, position),
+                    this.generic1, DELTA);
         }
 
         public void verifyMatchesHistorical(MotionEvent that, int pointerIndex, int position) {
@@ -334,6 +352,10 @@
                     that.getHistoricalAxisValue(MotionEvent.AXIS_ORIENTATION,
                             pointerIndex, position),
                     this.orientation, DELTA);
+
+            assertEquals("Generic axis 1 should be the same",
+                    that.getHistoricalAxisValue(MotionEvent.AXIS_GENERIC_1, pointerIndex, position),
+                    this.generic1, DELTA);
         }
 
         public void verifyMatchesPointerCoords(PointerCoords that) {
@@ -373,6 +395,9 @@
                     that.orientation, this.orientation, DELTA);
             assertEquals("Orientation should be the same",
                     that.getAxisValue(MotionEvent.AXIS_ORIENTATION), this.orientation, DELTA);
+
+            assertEquals("Generic axis 1 should be the same",
+                    that.getAxisValue(MotionEvent.AXIS_GENERIC_1), this.generic1, DELTA);
         }
 
         public void verifyMatchesPointerCoords(MotionEvent motionEvent, int pointerIndex) {
diff --git a/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java b/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
index e39f1cb..6ed2d34 100644
--- a/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
+++ b/tests/tests/view/src/android/view/cts/MotionEvent_PointerCoordsTest.java
@@ -40,7 +40,7 @@
     @Before
     public void setup() {
         mBuilder = withCoords(10.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
-                withTouch(3.0f, 2.4f);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f);
         mPointerCoords = mBuilder.build();
     }
 
@@ -54,42 +54,56 @@
         // Change value of X
         mPointerCoords.setAxisValue(MotionEvent.AXIS_X, 15.0f);
         withCoords(15.0f, 20.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of Y
         mPointerCoords.setAxisValue(MotionEvent.AXIS_Y, 25.0f);
         withCoords(15.0f, 25.0f).withPressure(1.2f).withSize(2.0f).withTool(1.2f, 1.4f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of pressure
         mPointerCoords.setAxisValue(MotionEvent.AXIS_PRESSURE, 2.2f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(2.0f).withTool(1.2f, 1.4f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of size
         mPointerCoords.setAxisValue(MotionEvent.AXIS_SIZE, 10.0f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(1.2f, 1.4f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of tool major
         mPointerCoords.setAxisValue(MotionEvent.AXIS_TOOL_MAJOR, 7.0f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 1.4f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of tool minor
         mPointerCoords.setAxisValue(MotionEvent.AXIS_TOOL_MINOR, 2.0f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
-                withTouch(3.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(3.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of tool major
         mPointerCoords.setAxisValue(MotionEvent.AXIS_TOUCH_MAJOR, 5.0f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
-                withTouch(5.0f, 2.4f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(5.0f, 2.4f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
 
         // Change value of tool minor
         mPointerCoords.setAxisValue(MotionEvent.AXIS_TOUCH_MINOR, 2.1f);
         withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f).
-                withTouch(5.0f, 2.1f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(5.0f, 2.1f).withGenericAxis1(4.4f).verifyMatchesPointerCoords(
+                mPointerCoords);
+
+        // Change value of axis generic 1
+        mPointerCoords.setAxisValue(MotionEvent.AXIS_GENERIC_1, 4.6f);
+        withCoords(15.0f, 25.0f).withPressure(2.2f).withSize(10.0f).withTool(7.0f, 2.0f)
+                .withTouch(5.0f, 2.1f).withGenericAxis1(4.6f).verifyMatchesPointerCoords(
+                mPointerCoords);
     }
 
     @Test
@@ -103,6 +117,7 @@
     public void testClear() {
         mPointerCoords.clear();
         withCoords(0.0f, 0.0f).withPressure(0.0f).withSize(0.0f).withTool(0.0f, 0.0f).
-                withTouch(0.0f, 0.0f).verifyMatchesPointerCoords(mPointerCoords);
+                withTouch(0.0f, 0.0f).withGenericAxis1(0.0f).verifyMatchesPointerCoords(
+                mPointerCoords);
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java
new file mode 100644
index 0000000..841ffe9
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.Dialog;
+import android.view.OnBackInvokedCallback;
+import android.view.OnBackInvokedDispatcher;
+import android.view.View;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test {@link OnBackInvokedDispatcher}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class OnBackInvokedDispatcherTest {
+    private View mView;
+    private OnBackInvokedDispatcherTestActivity mActivity;
+    private Dialog mDialog;
+    private View mViewInDialog;
+
+    @Rule
+    public ActivityTestRule<OnBackInvokedDispatcherTestActivity> mActivityRule =
+            new ActivityTestRule<>(OnBackInvokedDispatcherTestActivity.class);
+
+    @Before
+    public void setUp() {
+        mActivity = mActivityRule.getActivity();
+        mView = mActivity.findViewById(R.id.test_view);
+        mDialog = mActivity.getDialog();
+        mViewInDialog = mDialog.findViewById(R.id.test_view_in_dialog);
+    }
+
+    @Test
+    public void testGetDispatcherOnView() {
+        OnBackInvokedDispatcher viewDispatcher = mView.getOnBackInvokedDispatcher();
+        assertEquals(viewDispatcher, mActivity.getOnBackInvokedDispatcher());
+        assertNotNull("OnBackInvokedDispatcher on View should not be null", viewDispatcher);
+    }
+
+    @Test
+    public void testGetDispatcherOnDialog() {
+        OnBackInvokedDispatcher dialogDispatcher = mDialog.getOnBackInvokedDispatcher();
+        OnBackInvokedDispatcher dialogViewDispatcher = mViewInDialog.getOnBackInvokedDispatcher();
+        assertEquals(dialogDispatcher, dialogViewDispatcher);
+        assertNotNull("OnBackInvokedDispatcher on Dialog should not be null", dialogDispatcher);
+    }
+
+    @Test
+    public void testRegisterAndUnregisterCallbacks() {
+        OnBackInvokedDispatcher dispatcher = mView.getOnBackInvokedDispatcher();
+        OnBackInvokedCallback callback1 = createBackCallback();
+        OnBackInvokedCallback callback2 = createBackCallback();
+        dispatcher.registerOnBackInvokedCallback(
+                callback1, OnBackInvokedDispatcher.PRIORITY_OVERLAY);
+        dispatcher.registerOnBackInvokedCallback(
+                callback2, OnBackInvokedDispatcher.PRIORITY_DEFAULT);
+        dispatcher.unregisterOnBackInvokedCallback(callback2);
+        dispatcher.unregisterOnBackInvokedCallback(callback1);
+        dispatcher.unregisterOnBackInvokedCallback(callback2);
+    }
+
+    private OnBackInvokedCallback createBackCallback() {
+        return new OnBackInvokedCallback() {
+            @Override
+            public void onBackInvoked() {}
+        };
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.java b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.java
new file mode 100644
index 0000000..233147c
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/OnBackInvokedDispatcherTestActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.os.Bundle;
+
+public class OnBackInvokedDispatcherTestActivity extends Activity {
+    private Dialog mDialog;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.onbackinvokeddispatcher_layout);
+        mDialog = new Dialog(this, 0);
+        mDialog.setContentView(R.layout.onbackinvokeddispatcher_dialog_layout);
+        mDialog.show();
+    }
+
+    public Dialog getDialog() {
+        return mDialog;
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/SurfaceControlTest.java b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
new file mode 100644
index 0000000..06ca815
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SurfaceControlTest.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.cts;
+
+import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule;
+import static android.view.cts.util.ASurfaceControlTestUtils.getQuadrantBuffer;
+import static android.view.cts.util.ASurfaceControlTestUtils.getSolidBuffer;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.MultiRectChecker;
+import android.view.cts.surfacevalidator.ASurfaceControlTestActivity.PixelChecker;
+import android.view.cts.surfacevalidator.PixelColor;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class SurfaceControlTest {
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    private static final String TAG = SurfaceControlTest.class.getSimpleName();
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_LAYOUT_WIDTH = 100;
+    private static final int DEFAULT_LAYOUT_HEIGHT = 100;
+
+    private static final PixelColor RED = new PixelColor(PixelColor.RED);
+    private static final PixelColor BLUE = new PixelColor(PixelColor.BLUE);
+    private static final PixelColor MAGENTA = new PixelColor(PixelColor.MAGENTA);
+    private static final PixelColor GREEN = new PixelColor(PixelColor.GREEN);
+    private static final PixelColor YELLOW = new PixelColor(PixelColor.YELLOW);
+
+    @Rule
+    public ActivityScenarioRule<ASurfaceControlTestActivity> mActivityRule =
+            createFullscreenActivityScenarioRule(ASurfaceControlTestActivity.class);
+
+    @Rule
+    public TestName mName = new TestName();
+
+    private ASurfaceControlTestActivity mActivity;
+
+    @Before
+    public void setup() {
+        mActivityRule.getScenario().onActivity(activity -> mActivity = activity);
+    }
+
+    SurfaceControl getHostSurfaceControl() {
+        return mActivity.getSurfaceControl();
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // SurfaceHolder.Callbacks
+    ///////////////////////////////////////////////////////////////////////////
+
+    private abstract class BasicSurfaceHolderCallback implements SurfaceHolder.Callback {
+        private final Set<SurfaceControl> mSurfaceControls = new HashSet<>();
+        private final Set<HardwareBuffer> mBuffers = new HashSet<>();
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+            Canvas canvas = holder.lockCanvas();
+            canvas.drawColor(Color.YELLOW);
+            holder.unlockCanvasAndPost(canvas);
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
+            for (SurfaceControl surfaceControl : mSurfaceControls) {
+                transaction.reparent(surfaceControl, null);
+            }
+            transaction.apply();
+            mSurfaceControls.clear();
+
+            for (HardwareBuffer buffer : mBuffers) {
+                buffer.close();
+            }
+            mBuffers.clear();
+        }
+
+        public SurfaceControl createFromWindow(SurfaceHolder surfaceHolder) {
+            assertNotNull("No parent?", getHostSurfaceControl());
+            SurfaceControl surfaceControl = new SurfaceControl.Builder()
+                    .setParent(getHostSurfaceControl())
+                    .setName("SurfaceControl_createFromWindowLayer")
+                    .setHidden(false)
+                    .build();
+            mSurfaceControls.add(surfaceControl);
+            return surfaceControl;
+        }
+
+        public SurfaceControl create(SurfaceControl parentSurfaceControl) {
+            assertNotNull("No parent?", parentSurfaceControl);
+            SurfaceControl surfaceControl = new SurfaceControl.Builder()
+                    .setParent(parentSurfaceControl)
+                    .setName("SurfaceControl_create")
+                    .setHidden(false)
+                    .build();
+            mSurfaceControls.add(surfaceControl);
+            return surfaceControl;
+        }
+
+        public void setSolidBuffer(SurfaceControl surfaceControl,
+                int width, int height, int color) {
+            HardwareBuffer buffer = getSolidBuffer(width, height, color);
+            assertNotNull("failed to make solid buffer", buffer);
+            new SurfaceControl.Transaction()
+                    .setBuffer(surfaceControl, buffer)
+                    .apply();
+            mBuffers.add(buffer);
+        }
+
+        public void setQuadrantBuffer(SurfaceControl surfaceControl,
+                int width, int height, int colorTopLeft, int colorTopRight, int colorBottomRight,
+                int colorBottomLeft) {
+            HardwareBuffer buffer = getQuadrantBuffer(width, height, colorTopLeft, colorTopRight,
+                    colorBottomRight, colorBottomLeft);
+            assertNotNull("failed to make solid buffer", buffer);
+            new SurfaceControl.Transaction()
+                    .setBuffer(surfaceControl, buffer)
+                    .apply();
+            mBuffers.add(buffer);
+        }
+
+        public void setSourceToDefaultDest(SurfaceControl surfaceControl, Rect src) {
+            float scaleY = DEFAULT_LAYOUT_HEIGHT / (float) src.height();
+            float scaleX = DEFAULT_LAYOUT_WIDTH / (float) src.width();
+            new SurfaceControl.Transaction()
+                    .setCrop(surfaceControl, new Rect(0, 0,
+                            DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT))
+                    .setPosition(surfaceControl, -src.left * scaleX, -src.top * scaleY)
+                    .setScale(surfaceControl, scaleX, scaleY)
+                    .apply();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // Tests
+    ///////////////////////////////////////////////////////////////////////////
+
+    private void verifyTest(SurfaceHolder.Callback callback, PixelChecker pixelChecker) {
+        mActivity.verifyTest(callback, pixelChecker, mName);
+    }
+
+    // INTRO: The following tests run a series of commands and verify the
+    // output based on the number of pixels with a certain color on the display.
+    //
+    // The interface being tested is a NDK api but the only way to record the display
+    // through public apis is in through the SDK. So the test logic and test verification
+    // is in Java but the hooks that call into the NDK api are jni code.
+    //
+    // The set up is done during the surfaceCreated callback. In most cases, the
+    // test uses the opportunity to create a child layer through createFromWindow and
+    // performs operations on the child layer.
+    //
+    // When there is no visible buffer for the layer(s) the color defaults to black.
+    // The test cases allow a +/- 10% error rate. This is based on the error
+    // rate allowed in the SurfaceViewSyncTests
+
+    @Test
+    public void testSurfaceControl_createFromWindow() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        createFromWindow(holder);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceControl_create() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceControl_acquire() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer_parentAndChild() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(parentSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBuffer_childOnly() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setVisibility_show() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setVisibility(surfaceControl, true)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setVisibility_hide() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setVisibility(surfaceControl, false)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferOpaque_opaque() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.TRANSLUCENT_RED);
+                        new SurfaceControl.Transaction()
+                                .setOpaque(surfaceControl, true)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setBufferOpaque_translucent() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.TRANSLUCENT_RED);
+                        new SurfaceControl.Transaction()
+                                .setOpaque(surfaceControl, false)
+                                .apply();
+                    }
+                },
+                // setBufferOpaque is an optimization that can be used by SurfaceFlinger.
+                // It isn't required to affect SurfaceFlinger's behavior.
+                //
+                // Ideally we would check for a specific blending of red with a layer below
+                // it. Unfortunately we don't know what blending the layer will use and
+                // we don't know what variation the GPU/DPU/blitter might have. Although
+                // we don't know what shade of red might be present, we can at least check
+                // that the optimization doesn't cause the framework to drop the buffer entirely.
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_small() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(surfaceControl, .4f, .4f)
+                                .setPosition(surfaceControl, 10, 10)
+                                .apply();
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+                            return RED;
+                        }
+                        return YELLOW;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_smallScaleFirst() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(surfaceControl, .4f, .4f)
+                                .apply();
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl, 10, 10)
+                                .apply();
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+                            return RED;
+                        }
+                        return YELLOW;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_smallPositionFirst() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl, 10, 10)
+                                .apply();
+                        new SurfaceControl.Transaction()
+                                .setScale(surfaceControl, .4f, .4f)
+                                .apply();
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        if (x >= 10 && x < 50 && y >= 10 && y < 50) {
+                            return RED;
+                        }
+                        return YELLOW;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_parentSmall() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(parentSurfaceControl, .4f, .4f)
+                                .setPosition(parentSurfaceControl, 10, 10)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //1600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 1440 && pixelCount < 1760;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_childSmall() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(childSurfaceControl, .4f, .4f)
+                                .setPosition(childSurfaceControl, 10, 10)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //1600
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 1440 && pixelCount < 1760;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_extraLarge() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(surfaceControl, 3f, 3f)
+                                .setPosition(surfaceControl, -100, -100)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_childExtraLarge() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl parentSurfaceControl = createFromWindow(holder);
+                        SurfaceControl childSurfaceControl = create(parentSurfaceControl);
+
+                        setSolidBuffer(childSurfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setScale(childSurfaceControl, 3f, 3f)
+                                .setPosition(childSurfaceControl, -100, -100)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_negativeOffset() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl, -20, -30)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //5600 (w = 80, h = 70)
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 5000 && pixelCount < 6000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_outOfParentBounds() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl, 50, 50)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) { //2500
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 2250 && pixelCount < 2750;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDestinationRect_twoLayers() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.BLUE);
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl1, 10, 10)
+                                .setScale(surfaceControl1, .2f, .3f)
+                                .apply();
+                        new SurfaceControl.Transaction()
+                                .setPosition(surfaceControl2, 70, 20)
+                                .setScale(surfaceControl2, .2f, .3f)
+                                .apply();
+                    }
+                },
+
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        if (x >= 10 && x < 30 && y >= 10 && y < 40) {
+                            return RED;
+                        } else if (x >= 70 && x < 90 && y >= 20 && y < 50) {
+                            return BLUE;
+                        } else {
+                            return YELLOW;
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                    }
+                },
+
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return RED;
+                        } else if (x >= halfWidth && y < halfHeight) {
+                            return BLUE;
+                        } else if (x < halfWidth && y >= halfHeight) {
+                            return GREEN;
+                        } else {
+                            return MAGENTA;
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_small() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setSourceToDefaultDest(surfaceControl, new Rect(60, 10, 90, 90));
+                    }
+                },
+                new PixelChecker(PixelColor.MAGENTA) { //5000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 4500 && pixelCount < 5500;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setSourceRect_smallCentered() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setSourceToDefaultDest(surfaceControl, new Rect(40, 40, 60, 60));
+                    }
+                },
+
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return RED;
+                        } else if (x >= halfWidth && y < halfHeight) {
+                            return BLUE;
+                        } else if (x < halfWidth && y >= halfHeight) {
+                            return GREEN;
+                        } else {
+                            return MAGENTA;
+                        }
+                    }
+
+                    @Override
+                    public boolean checkPixels(int matchingPixelCount, int width, int height) {
+                        // There will be sampling artifacts along the center line, ignore those
+                        return matchingPixelCount > 9000 && matchingPixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setCropRect_extraLarge() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        new SurfaceControl.Transaction()
+                                .setCrop(surfaceControl, new Rect(-50, -50, 150, 150))
+                                .apply();
+                    }
+                },
+
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return RED;
+                        } else if (x >= halfWidth && y < halfHeight) {
+                            return BLUE;
+                        } else if (x < halfWidth && y >= halfHeight) {
+                            return GREEN;
+                        } else {
+                            return MAGENTA;
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setCropRect_badOffset() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        new SurfaceControl.Transaction()
+                                .setCrop(surfaceControl, new Rect(-50, -50, 50, 50))
+                                .apply();
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return RED;
+                        } else {
+                            return YELLOW;
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setTransform_flipH() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        new SurfaceControl.Transaction()
+                                .setBufferTransform(surfaceControl, 1)
+                                .apply();
+                    }
+                },
+                new MultiRectChecker(new Rect(0, 0, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT)) {
+                    @Override
+                    public PixelColor getExpectedColor(int x, int y) {
+                        int halfWidth = DEFAULT_LAYOUT_WIDTH / 2;
+                        int halfHeight = DEFAULT_LAYOUT_HEIGHT / 2;
+                        if (x < halfWidth && y < halfHeight) {
+                            return BLUE;
+                        } else if (x >= halfWidth && y < halfHeight) {
+                            return RED;
+                        } else if (x < halfWidth && y >= halfHeight) {
+                            return MAGENTA;
+                        } else {
+                            return GREEN;
+                        }
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setTransform_rotate180() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+
+                        setQuadrantBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.RED, PixelColor.BLUE,
+                                PixelColor.MAGENTA, PixelColor.GREEN);
+                        setSourceToDefaultDest(surfaceControl, new Rect(0, 50, 50, 100));
+                        new SurfaceControl.Transaction()
+                                .setBufferTransform(surfaceControl, 3)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.BLUE) { // 10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setDamageRegion_all() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+
+                        HardwareBuffer blueBuffer = getSolidBuffer(DEFAULT_LAYOUT_WIDTH,
+                                DEFAULT_LAYOUT_HEIGHT, PixelColor.BLUE);
+                        Region damageRegion = new Region();
+                        damageRegion.op(0, 0, 25, 25, Region.Op.UNION);
+                        damageRegion.op(25, 25, 100, 100, Region.Op.UNION);
+                        new SurfaceControl.Transaction()
+                                .setBuffer(surfaceControl, blueBuffer)
+                                .setDamageRegion(surfaceControl, damageRegion)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.BLUE) { //10000
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount > 9000 && pixelCount < 11000;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_zero() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        new SurfaceControl.Transaction()
+                                .setLayer(surfaceControl1, 1)
+                                .setLayer(surfaceControl2, 0)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_positive() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        new SurfaceControl.Transaction()
+                                .setLayer(surfaceControl1, 1)
+                                .setLayer(surfaceControl2, 5)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_negative() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        new SurfaceControl.Transaction()
+                                .setLayer(surfaceControl1, 1)
+                                .setLayer(surfaceControl2, -15)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_max() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        new SurfaceControl.Transaction()
+                                .setLayer(surfaceControl1, 1)
+                                .setLayer(surfaceControl2, Integer.MAX_VALUE)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.RED) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+
+    @Test
+    public void testSurfaceTransaction_setZOrder_min() {
+        verifyTest(
+                new BasicSurfaceHolderCallback() {
+                    @Override
+                    public void surfaceCreated(SurfaceHolder holder) {
+                        SurfaceControl surfaceControl1 = createFromWindow(holder);
+                        SurfaceControl surfaceControl2 = createFromWindow(holder);
+                        setSolidBuffer(surfaceControl1, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.RED);
+                        setSolidBuffer(surfaceControl2, DEFAULT_LAYOUT_WIDTH, DEFAULT_LAYOUT_HEIGHT,
+                                PixelColor.MAGENTA);
+
+                        new SurfaceControl.Transaction()
+                                .setLayer(surfaceControl1, 1)
+                                .setLayer(surfaceControl2, Integer.MIN_VALUE)
+                                .apply();
+                    }
+                },
+                new PixelChecker(PixelColor.YELLOW) {
+                    @Override
+                    public boolean checkPixels(int pixelCount, int width, int height) {
+                        return pixelCount == 0;
+                    }
+                });
+    }
+}
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
index 645d9fa..7cd613c 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTest.java
@@ -177,6 +177,38 @@
         return makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY));
     };
 
+    private static AnimationFactory sFixedSizeWithViewSizeAnimationFactory = view -> {
+        ValueAnimator anim = ValueAnimator.ofInt(0, 100);
+        anim.addUpdateListener(valueAnimator -> {
+            ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
+            layoutParams.width++;
+            if (layoutParams.height++ > 1500) {
+                layoutParams.height = 320;
+                layoutParams.width = 240;
+            }
+            view.setLayoutParams(layoutParams);
+
+            if ((Integer) valueAnimator.getAnimatedValue() % 3 == 0) {
+                ((SurfaceView) view).getHolder().setFixedSize(320, 240);
+            } else if ((Integer) valueAnimator.getAnimatedValue() % 7 == 0) {
+                ((SurfaceView) view).getHolder().setFixedSize(1280, 960);
+            }
+        });
+        return makeInfinite(anim);
+    };
+
+    private static AnimationFactory sFixedSizeAnimationFactory = view -> {
+        ValueAnimator anim = ValueAnimator.ofInt(0, 100);
+        anim.addUpdateListener(valueAnimator -> {
+            if ((Integer) valueAnimator.getAnimatedValue() % 2 == 0) {
+                ((SurfaceView) view).getHolder().setFixedSize(320, 240);
+            } else {
+                ((SurfaceView) view).getHolder().setFixedSize(1280, 960);
+            }
+        });
+        return makeInfinite(anim);
+    };
+
     private AnimationFactory sTranslateAnimationFactory = view -> {
         PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 10f, 30f);
         PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f);
@@ -263,6 +295,43 @@
                 }), mName);
     }
 
+
+    /**
+     * Change requested surface size and SurfaceView size and verify buffers always fill to
+     * SurfaceView size. b/190449942
+     */
+    @Test
+    public void testSurfaceViewFixedSizeWithViewSizeChanges() throws Throwable {
+        mActivity.verifyTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sFixedSizeWithViewSizeAnimationFactory,
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
+    }
+
+    /**
+     * Change requested surface size and verify buffers always fill to SurfaceView size.
+     * b/194458377
+     */
+    @Test
+    public void testSurfaceViewFixedSizeChanges() throws Throwable {
+        mActivity.verifyTest(new AnimationTestCase(
+                sVideoViewFactory,
+                new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
+                sFixedSizeAnimationFactory,
+                new PixelChecker() {
+                    @Override
+                    public boolean checkPixels(int blackishPixelCount, int width, int height) {
+                        return blackishPixelCount == 0;
+                    }
+                }), mName);
+    }
+
     @Test
     public void testVideoSurfaceViewTranslate() throws Throwable {
         mActivity.verifyTest(new AnimationTestCase(
diff --git a/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java b/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
index c74bd43..c4bfb11 100644
--- a/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
+++ b/tests/tests/view/src/android/view/cts/VerifyInputEventTest.java
@@ -34,6 +34,7 @@
 import android.graphics.Point;
 import android.hardware.input.InputManager;
 import android.os.SystemClock;
+import android.view.InputDevice;
 import android.view.InputEvent;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
@@ -65,7 +66,7 @@
 @RunWith(AndroidJUnit4.class)
 public class VerifyInputEventTest {
     private static final int NANOS_PER_MILLISECOND = 1000000;
-    private static final float STRICT_TOLERANCE = 0;
+    private static final float EPSILON = 0.001f;
     private static final int INJECTED_EVENT_DEVICE_ID = KeyCharacterMap.VIRTUAL_KEYBOARD;
 
     private InputManager mInputManager;
@@ -158,6 +159,7 @@
         final long downTime = SystemClock.uptimeMillis();
         MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
                 point.x, point.y, 0 /*metaState*/);
+        downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(downEvent, true);
         MotionEvent received = waitForMotion();
         VerifiedInputEvent verified = mInputManager.verifyInputEvent(received);
@@ -168,6 +170,7 @@
         // Send UP event for consistency
         MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
+        upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(upEvent, true);
         waitForMotion();
     }
@@ -184,6 +187,7 @@
         final long downTime = SystemClock.uptimeMillis();
         MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
                 point.x, point.y, 0 /*metaState*/);
+        downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(downEvent, true);
         waitForMotion(); // we will not be using the received event
         VerifiedInputEvent verified = mInputManager.verifyInputEvent(downEvent);
@@ -192,6 +196,7 @@
         // Send UP event for consistency
         MotionEvent upEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_UP,
                 point.x, point.y, 0 /*metaState*/);
+        upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(upEvent, true);
         waitForMotion();
     }
@@ -207,6 +212,7 @@
         final long downTime = SystemClock.uptimeMillis();
         MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
                 point.x, point.y, 0 /*metaState*/);
+        downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(downEvent, true);
         MotionEvent received = waitForMotion();
         // use the received event, by modify its action
@@ -217,6 +223,7 @@
         // Send UP event for consistency
         MotionEvent upEvent = MotionEvent.obtain(downTime, SystemClock.uptimeMillis(),
                 MotionEvent.ACTION_UP, point.x, point.y, 0 /*metaState*/);
+        upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(upEvent, true);
         waitForMotion();
     }
@@ -263,6 +270,7 @@
         MotionEvent downEvent = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN,
                 point.x, point.y, 1 /*pressure*/, 1 /*size*/, 0 /*metaState*/,
                 0 /*xPrecision*/, 0 /*yPrecision*/, 1 /*deviceId*/, 0 /*edgeFlags*/);
+        downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(downEvent, true);
         MotionEvent received = waitForMotion();
         assertEquals(INJECTED_EVENT_DEVICE_ID, received.getDeviceId());
@@ -276,6 +284,7 @@
                 MotionEvent.ACTION_UP, point.x, point.y, 0 /*pressure*/, 1 /*size*/,
                 0 /*metaState*/, 0 /*xPrecision*/, 0 /*yPrecision*/,
                 1 /*deviceId*/, 0 /*edgeFlags*/);
+        upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
         mAutomation.injectInputEvent(upEvent, true);
         waitForMotion();
     }
@@ -327,8 +336,8 @@
         assertTrue(verified instanceof VerifiedMotionEvent);
         VerifiedMotionEvent verifiedMotion = (VerifiedMotionEvent) verified;
 
-        assertEquals(motionEvent.getRawX(), verifiedMotion.getRawX(), STRICT_TOLERANCE);
-        assertEquals(motionEvent.getRawY(), verifiedMotion.getRawY(), STRICT_TOLERANCE);
+        assertEquals(motionEvent.getRawX(), verifiedMotion.getRawX(), EPSILON);
+        assertEquals(motionEvent.getRawY(), verifiedMotion.getRawY(), EPSILON);
         assertEquals(motionEvent.getActionMasked(), verifiedMotion.getActionMasked());
         assertEquals(motionEvent.getDownTime() * NANOS_PER_MILLISECOND,
                 verifiedMotion.getDownTimeNanos());
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
index fb1cb13..32693b4 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceEnabledTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
index ff99963..ca53a15 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -23,6 +23,7 @@
 import android.app.Instrumentation;
 import android.view.InputDevice;
 import android.view.KeyEvent;
+import android.view.cts.R;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -30,7 +31,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 import com.android.cts.input.InputJsonParser;
 import com.android.cts.input.UinputDevice;
 
@@ -95,7 +96,7 @@
     @Before
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivityRule.getActivity());
         mParser = new InputJsonParser(mInstrumentation.getTargetContext());
         mKeyLayout = nativeLoadKeyLayout(mParser.readRegisterCommand(R.raw.Generic));
         mUinputDevice = new UinputDevice(mInstrumentation, DEVICE_ID, GOOGLE_VENDOR_ID,
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
index 5c77984..61efe0f 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceKeyLayoutMapTestActivity.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.fail;
 
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
index 31d5e6e..eb53bae 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceMultiDeviceKeyEventTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -32,7 +32,7 @@
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 import com.android.cts.input.UinputDevice;
 
 import org.json.JSONArray;
@@ -91,7 +91,7 @@
     @Before
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
-        PollingCheck.waitFor(mActivityRule.getActivity()::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivityRule.getActivity());
         for (int i = 0; i < NUM_DEVICES; i++) {
             final int jsonDeviceId = i + 1;
             mUinputDevices[i] = new UinputDevice(mInstrumentation, jsonDeviceId,
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
index a088a31..1d34150 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceSensorManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -36,13 +36,13 @@
 import android.os.SystemClock;
 import android.util.Log;
 import android.view.InputDevice;
+import android.view.cts.R;
 
 import androidx.annotation.NonNull;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
 
-import com.android.cts.input.InputJsonParser;
 import com.android.cts.input.UinputDevice;
 
 import org.junit.After;
@@ -102,12 +102,10 @@
 
     private InputManager mInputManager;
     private UinputDevice mUinputDevice;
-    private InputJsonParser mParser;
     private Instrumentation mInstrumentation;
     private SensorManager mSensorManager;
     private HandlerThread mSensorThread = null;
     private Handler mSensorHandler = null;
-    private int mDeviceId;
     private final Object mLock = new Object();
 
     private class Callback extends SensorManager.DynamicSensorCallback {
@@ -373,14 +371,10 @@
         mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
         assertNotNull(mInputManager);
 
-        mParser = new InputJsonParser(mInstrumentation.getTargetContext());
-        mDeviceId = mParser.readDeviceId(resourceId);
-        String registerCommand = mParser.readRegisterCommand(resourceId);
-        final int vendorId = mParser.readVendorId(resourceId);
-        final int productId = mParser.readProductId(resourceId);
-        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
-            vendorId, productId, InputDevice.SOURCE_KEYBOARD, registerCommand);
-        mSensorManager = getSensorManager(vendorId, productId);
+        mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.gamepad_sensors_register,
+                InputDevice.SOURCE_KEYBOARD);
+        mSensorManager = getSensorManager(mUinputDevice.getVendorId(),
+                mUinputDevice.getProductId());
         assertNotNull(mSensorManager);
 
         mSensorThread = new HandlerThread("SensorThread");
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
index fca9854..61445ee 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorManagerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -29,6 +29,7 @@
 import android.os.VibratorManager;
 import android.util.Log;
 import android.view.InputDevice;
+import android.view.cts.R;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -61,6 +62,7 @@
     private InputJsonParser mParser;
     private Instrumentation mInstrumentation;
     private VibratorManager mVibratorManager;
+    /** The device id inside the resource file (register command) */
     private int mDeviceId;
 
     /**
@@ -85,18 +87,16 @@
 
     @Before
     public void setup() {
-        final int resourceId = R.raw.google_gamepad_register;
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
         assertNotNull(mInputManager);
         mParser = new InputJsonParser(mInstrumentation.getTargetContext());
-        mDeviceId = mParser.readDeviceId(resourceId);
-        String registerCommand = mParser.readRegisterCommand(resourceId);
-        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
-                mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
-                InputDevice.SOURCE_KEYBOARD, registerCommand);
-        mVibratorManager = getVibratorManager(mParser.readVendorId(resourceId),
-                mParser.readProductId(resourceId));
+
+        mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.google_gamepad_register,
+                InputDevice.SOURCE_KEYBOARD);
+        mDeviceId = mUinputDevice.getRegisterCommandDeviceId();
+        mVibratorManager = getVibratorManager(mUinputDevice.getVendorId(),
+                mUinputDevice.getProductId());
         assertTrue(mVibratorManager != null);
     }
 
diff --git a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
index f2d3397..fc81cbd 100644
--- a/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
+++ b/tests/tests/view/src/android/view/cts/input/InputDeviceVibratorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.view.cts;
+package android.view.cts.input;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -34,6 +34,7 @@
 import android.os.Vibrator.OnVibratorStateChangedListener;
 import android.util.Log;
 import android.view.InputDevice;
+import android.view.cts.R;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -72,6 +73,7 @@
     private InputJsonParser mParser;
     private Instrumentation mInstrumentation;
     private Vibrator mVibrator;
+    /** The device id inside the resource file (register command) */
     private int mDeviceId;
 
     @Rule
@@ -102,20 +104,16 @@
 
     @Before
     public void setup() {
-        final int resourceId = R.raw.google_gamepad_register;
-
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mInputManager = mInstrumentation.getTargetContext().getSystemService(InputManager.class);
         assertNotNull(mInputManager);
         mParser = new InputJsonParser(mInstrumentation.getTargetContext());
-        mDeviceId = mParser.readDeviceId(resourceId);
-        String registerCommand = mParser.readRegisterCommand(resourceId);
-        mUinputDevice = new UinputDevice(mInstrumentation, mDeviceId,
-                mParser.readVendorId(resourceId), mParser.readProductId(resourceId),
-                InputDevice.SOURCE_KEYBOARD, registerCommand);
-        mVibrator = getVibrator(mParser.readVendorId(resourceId),
-                mParser.readProductId(resourceId));
-        assertTrue(mVibrator != null);
+
+        mUinputDevice = UinputDevice.create(mInstrumentation, R.raw.google_gamepad_register,
+                InputDevice.SOURCE_KEYBOARD);
+        mDeviceId = mUinputDevice.getRegisterCommandDeviceId();
+        mVibrator = getVibrator(mUinputDevice.getVendorId(), mUinputDevice.getProductId());
+        assertNotNull(mVibrator);
         mVibrator.addVibratorStateListener(mListener);
         verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS)
                 .times(1)).onVibratorStateChanged(false);
diff --git a/tests/tests/view/src/android/view/cts/input/InputEventTest.java b/tests/tests/view/src/android/view/cts/input/InputEventTest.java
deleted file mode 100644
index 6ce80d3..0000000
--- a/tests/tests/view/src/android/view/cts/input/InputEventTest.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.view.cts.input;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-
-import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.Map;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class InputEventTest {
-
-    @Test
-    public void testKeyCodeToString() {
-        assertEquals("KEYCODE_UNKNOWN", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_UNKNOWN));
-        assertEquals("KEYCODE_HOME", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_HOME));
-        assertEquals("KEYCODE_0", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_0));
-        assertEquals("KEYCODE_POWER", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_POWER));
-        assertEquals("KEYCODE_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_A));
-        assertEquals("KEYCODE_SPACE", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_SPACE));
-        assertEquals("KEYCODE_MENU", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_MENU));
-        assertEquals("KEYCODE_BACK", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BACK));
-        assertEquals("KEYCODE_BUTTON_A", KeyEvent.keyCodeToString(KeyEvent.KEYCODE_BUTTON_A));
-        assertEquals("KEYCODE_PROFILE_SWITCH",
-                        KeyEvent.keyCodeToString(KeyEvent.KEYCODE_PROFILE_SWITCH));
-    }
-
-    @Test
-    public void testAxisFromToString() {
-        final Map<Integer, String> axes = new ArrayMap<Integer, String>();
-        axes.put(MotionEvent.AXIS_X, "AXIS_X");
-        axes.put(MotionEvent.AXIS_Y, "AXIS_Y");
-        axes.put(MotionEvent.AXIS_PRESSURE, "AXIS_PRESSURE");
-        axes.put(MotionEvent.AXIS_SIZE, "AXIS_SIZE");
-        axes.put(MotionEvent.AXIS_TOUCH_MAJOR, "AXIS_TOUCH_MAJOR");
-        axes.put(MotionEvent.AXIS_TOUCH_MINOR, "AXIS_TOUCH_MINOR");
-        axes.put(MotionEvent.AXIS_TOOL_MAJOR, "AXIS_TOOL_MAJOR");
-        axes.put(MotionEvent.AXIS_TOOL_MINOR, "AXIS_TOOL_MINOR");
-        axes.put(MotionEvent.AXIS_ORIENTATION, "AXIS_ORIENTATION");
-        axes.put(MotionEvent.AXIS_VSCROLL, "AXIS_VSCROLL");
-        axes.put(MotionEvent.AXIS_HSCROLL, "AXIS_HSCROLL");
-        axes.put(MotionEvent.AXIS_Z, "AXIS_Z");
-        axes.put(MotionEvent.AXIS_RX, "AXIS_RX");
-        axes.put(MotionEvent.AXIS_RY, "AXIS_RY");
-        axes.put(MotionEvent.AXIS_RZ, "AXIS_RZ");
-        axes.put(MotionEvent.AXIS_HAT_X, "AXIS_HAT_X");
-        axes.put(MotionEvent.AXIS_HAT_Y, "AXIS_HAT_Y");
-        axes.put(MotionEvent.AXIS_LTRIGGER, "AXIS_LTRIGGER");
-        axes.put(MotionEvent.AXIS_RTRIGGER, "AXIS_RTRIGGER");
-        axes.put(MotionEvent.AXIS_THROTTLE, "AXIS_THROTTLE");
-        axes.put(MotionEvent.AXIS_RUDDER, "AXIS_RUDDER");
-        axes.put(MotionEvent.AXIS_WHEEL, "AXIS_WHEEL");
-        axes.put(MotionEvent.AXIS_GAS, "AXIS_GAS");
-        axes.put(MotionEvent.AXIS_BRAKE, "AXIS_BRAKE");
-        axes.put(MotionEvent.AXIS_DISTANCE, "AXIS_DISTANCE");
-        axes.put(MotionEvent.AXIS_TILT, "AXIS_TILT");
-        axes.put(MotionEvent.AXIS_SCROLL, "AXIS_SCROLL");
-        axes.put(MotionEvent.AXIS_RELATIVE_X, "AXIS_RELATIVE_X");
-        axes.put(MotionEvent.AXIS_RELATIVE_Y, "AXIS_RELATIVE_Y");
-        axes.put(MotionEvent.AXIS_GENERIC_1, "AXIS_GENERIC_1");
-        axes.put(MotionEvent.AXIS_GENERIC_2, "AXIS_GENERIC_2");
-        axes.put(MotionEvent.AXIS_GENERIC_3, "AXIS_GENERIC_3");
-        axes.put(MotionEvent.AXIS_GENERIC_4, "AXIS_GENERIC_4");
-        axes.put(MotionEvent.AXIS_GENERIC_5, "AXIS_GENERIC_5");
-        axes.put(MotionEvent.AXIS_GENERIC_6, "AXIS_GENERIC_6");
-        axes.put(MotionEvent.AXIS_GENERIC_7, "AXIS_GENERIC_7");
-        axes.put(MotionEvent.AXIS_GENERIC_8, "AXIS_GENERIC_8");
-        axes.put(MotionEvent.AXIS_GENERIC_9, "AXIS_GENERIC_9");
-        axes.put(MotionEvent.AXIS_GENERIC_10, "AXIS_GENERIC_10");
-        axes.put(MotionEvent.AXIS_GENERIC_11, "AXIS_GENERIC_11");
-        axes.put(MotionEvent.AXIS_GENERIC_12, "AXIS_GENERIC_12");
-        axes.put(MotionEvent.AXIS_GENERIC_13, "AXIS_GENERIC_13");
-        axes.put(MotionEvent.AXIS_GENERIC_14, "AXIS_GENERIC_14");
-        axes.put(MotionEvent.AXIS_GENERIC_15, "AXIS_GENERIC_15");
-        axes.put(MotionEvent.AXIS_GENERIC_16, "AXIS_GENERIC_16");
-        // As Axes values definition is not continuous from AXIS_RELATIVE_Y to AXIS_GENERIC_1,
-        // Need to verify MotionEvent.axisToString returns axis name correctly.
-        // Also verify that we are not crashing on those calls, and that the return result on each
-        // is not empty. We do expect the two-way call chain of to/from to get us back to the
-        // original integer value.
-        for (Map.Entry<Integer, String> entry : axes.entrySet()) {
-            final int axis = entry.getKey();
-            String axisToString = MotionEvent.axisToString(entry.getKey());
-            assertFalse(TextUtils.isEmpty(axisToString));
-            assertEquals(axisToString, entry.getValue());
-            assertEquals(axis, MotionEvent.axisFromString(axisToString));
-        }
-    }
-}
diff --git a/tests/tests/view/src/android/view/cts/input/OWNERS b/tests/tests/view/src/android/view/cts/input/OWNERS
index 50445ea..bbaec4d 100644
--- a/tests/tests/view/src/android/view/cts/input/OWNERS
+++ b/tests/tests/view/src/android/view/cts/input/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 136048
-lzye@google.com
 michaelwr@google.com
+prabirmsp@google.com
 svv@google.com
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java b/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
index a0926b7..6049389 100644
--- a/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
+++ b/tests/tests/view/src/android/view/cts/util/ASurfaceControlTestUtils.java
@@ -19,6 +19,7 @@
 import static org.junit.Assert.assertTrue;
 
 import android.graphics.Rect;
+import android.hardware.HardwareBuffer;
 import android.view.Surface;
 
 public class ASurfaceControlTestUtils {
@@ -170,4 +171,10 @@
             long surfaceTransaction, boolean waitForFence, TransactionCompleteListener listener);
     public static native void nSurfaceTransaction_setOnCommitCallbackWithoutContext(
             long surfaceTransaction, TransactionCompleteListener listener);
+    public static native void nSurfaceTransaction_setFrameTimeline(long surfaceTransaction,
+            long vsyncId);
+
+    public static native HardwareBuffer getSolidBuffer(int width, int height, int color);
+    public static native HardwareBuffer getQuadrantBuffer(int width, int height,
+            int colorTopLeft, int colorTopRight, int colorBottomRight, int colorBottomLeft);
 }
diff --git a/tests/tests/view/src/android/view/cts/util/FrameCallbackData.java b/tests/tests/view/src/android/view/cts/util/FrameCallbackData.java
new file mode 100644
index 0000000..9e5cd68
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/util/FrameCallbackData.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.view.cts.util;
+
+public class FrameCallbackData {
+    static {
+        System.loadLibrary("ctsview_jni");
+    }
+
+    public static class FrameTimeline {
+        FrameTimeline(long vsyncId, long expectedPresentTime, long deadline) {
+            mVsyncId = vsyncId;
+            mExpectedPresentTime = expectedPresentTime;
+            mDeadline = deadline;
+        }
+
+        public long getVsyncId() {
+            return mVsyncId;
+        }
+
+        public long getExpectedPresentTime() {
+            return mExpectedPresentTime;
+        }
+
+        public long getDeadline() {
+            return mDeadline;
+        }
+
+        private long mVsyncId;
+        private long mExpectedPresentTime;
+        private long mDeadline;
+    }
+
+    FrameCallbackData(
+            FrameTimeline[] frameTimelines, int preferredFrameTimelineIndex) {
+        mFrameTimelines = frameTimelines;
+        mPreferredFrameTimelineIndex = preferredFrameTimelineIndex;
+    }
+
+    public FrameTimeline[] getFrameTimelines() {
+        return mFrameTimelines;
+    }
+
+    public int getPreferredFrameTimelineIndex() {
+        return mPreferredFrameTimelineIndex;
+    }
+
+    private FrameTimeline[] mFrameTimelines;
+    private int mPreferredFrameTimelineIndex;
+
+    public static native FrameCallbackData nGetFrameTimelines();
+}
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
index ccecf85..6bdeeb3 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/ASurfaceControlTestActivity.java
@@ -29,19 +29,28 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.os.Bundle;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.PointerIcon;
+import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
+import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 
 import androidx.annotation.NonNull;
 
+import org.junit.Assert;
+import org.junit.rules.TestName;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -53,7 +62,7 @@
     private static final int DEFAULT_LAYOUT_HEIGHT = 100;
     private static final int OFFSET_X = 100;
     private static final int OFFSET_Y = 100;
-    private static final long WAIT_TIMEOUT_S = 5;
+    public static final long WAIT_TIMEOUT_S = 5;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private volatile boolean mOnWatch;
@@ -103,8 +112,26 @@
         mInstrumentation = getInstrumentation();
     }
 
+    public SurfaceControl getSurfaceControl() {
+        return mSurfaceView.getSurfaceControl();
+    }
+
     public void verifyTest(SurfaceHolder.Callback surfaceHolderCallback,
-            PixelChecker pixelChecker) {
+            PixelChecker pixelChecker, TestName name) {
+        final CountDownLatch readyFence = new CountDownLatch(1);
+        SurfaceHolderCallback surfaceHolderCallbackWrapper = new SurfaceHolderCallback(
+                surfaceHolderCallback,
+                readyFence, mParent.getViewTreeObserver());
+        createSurface(surfaceHolderCallbackWrapper);
+        try {
+            assertTrue("timeout", readyFence.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
+        } catch (InterruptedException e) {
+            Assert.fail("interrupted");
+        }
+        verifyScreenshot(pixelChecker, name);
+    }
+
+    public void createSurface(SurfaceHolderCallback surfaceHolderCallback) {
         try {
             mReadyToStart.await(5, TimeUnit.SECONDS);
         } catch (InterruptedException e) {
@@ -120,15 +147,13 @@
             return;
         }
 
-        final SurfaceHolderCallback surfaceHolderCallbackWrapper =
-                new SurfaceHolderCallback(surfaceHolderCallback);
         mHandler.post(() -> {
-            mSurfaceView.getHolder().addCallback(surfaceHolderCallbackWrapper);
+            mSurfaceView.getHolder().addCallback(surfaceHolderCallback);
             mParent.addView(mSurfaceView, mLayoutParams);
         });
-        mInstrumentation.waitForIdleSync();
-        surfaceHolderCallbackWrapper.waitForSurfaceCreated();
+    }
 
+    public void verifyScreenshot(PixelChecker pixelChecker, TestName name) {
         final CountDownLatch countDownLatch = new CountDownLatch(1);
         UiAutomation uiAutomation = mInstrumentation.getUiAutomation();
         mHandler.post(() -> {
@@ -151,6 +176,9 @@
         Rect bounds = pixelChecker.getBoundsToCheck(swBitmap);
         boolean success = pixelChecker.checkPixels(numMatchingPixels, swBitmap.getWidth(),
                 swBitmap.getHeight());
+        if (!success) {
+            saveFailureCapture(swBitmap, name);
+        }
         swBitmap.recycle();
 
         assertTrue("Actual matched pixels:" + numMatchingPixels
@@ -161,6 +189,10 @@
         return mSurfaceView;
     }
 
+    public FrameLayout getParentFrameLayout() {
+        return mParent;
+    }
+
     public abstract static class MultiRectChecker extends RectChecker {
         public MultiRectChecker(Rect boundsToCheck) {
             super(boundsToCheck);
@@ -267,16 +299,19 @@
     public static class SurfaceHolderCallback implements SurfaceHolder.Callback {
         private final SurfaceHolder.Callback mTestCallback;
         private final CountDownLatch mSurfaceCreatedLatch;
+        private final ViewTreeObserver mViewTreeObserver;
 
-        SurfaceHolderCallback(SurfaceHolder.Callback callback) {
+        public SurfaceHolderCallback(SurfaceHolder.Callback callback, CountDownLatch readyFence,
+                ViewTreeObserver viewTreeObserver) {
             mTestCallback = callback;
-            mSurfaceCreatedLatch = new CountDownLatch(1);
+            mSurfaceCreatedLatch = readyFence;
+            mViewTreeObserver = viewTreeObserver;
         }
 
         @Override
         public void surfaceCreated(@NonNull SurfaceHolder holder) {
             mTestCallback.surfaceCreated(holder);
-            mSurfaceCreatedLatch.countDown();
+            mViewTreeObserver.registerFrameCommitCallback(mSurfaceCreatedLatch::countDown);
         }
 
         @Override
@@ -289,12 +324,31 @@
         public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
             mTestCallback.surfaceDestroyed(holder);
         }
+    }
 
-        public void waitForSurfaceCreated() {
-            try {
-                mSurfaceCreatedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS);
-            } catch (InterruptedException e) {
+    private void saveFailureCapture(Bitmap failFrame, TestName name) {
+        String directoryName = Environment.getExternalStorageDirectory()
+                + "/" + getClass().getSimpleName()
+                + "/" + name.getMethodName();
+        File testDirectory = new File(directoryName);
+        if (testDirectory.exists()) {
+            String[] children = testDirectory.list();
+            for (String file : children) {
+                new File(testDirectory, file).delete();
             }
+        } else {
+            testDirectory.mkdirs();
+        }
+
+        String bitmapName = "frame.png";
+        Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
+
+        File file = new File(directoryName, bitmapName);
+        try (FileOutputStream fileStream = new FileOutputStream(file)) {
+            failFrame.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
+            fileStream.flush();
+        } catch (IOException e) {
+            e.printStackTrace();
         }
     }
 }
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
index ab71181..e3f4b4e 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/PixelColor.java
@@ -24,8 +24,7 @@
     public static final int MAGENTA = 0xFFFF00FF;
     public static final int WHITE = 0xFFFFFFFF;
 
-    public static final int TRANSPARENT_RED = 0x7F0000FF;
-    public static final int TRANSPARENT_BLUE = 0x7FFF0000;
+    public static final int TRANSLUCENT_RED = 0x7F0000FF;
     public static final int TRANSPARENT = 0x00000000;
 
     // Default to black
diff --git a/tests/tests/voiceRecognition/OWNERS b/tests/tests/voiceRecognition/OWNERS
index 88d73a9..01a9524 100644
--- a/tests/tests/voiceRecognition/OWNERS
+++ b/tests/tests/voiceRecognition/OWNERS
@@ -1,4 +1,10 @@
 # Bug component: 533220
+volnov@google.com
+andreaambu@google.com
+eugeniom@google.com
+schfan@google.com
+
+# Framework team as backup
 adamhe@google.com
 augale@google.com
 joannechung@google.com
diff --git a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
index 5188b54..18f1307 100644
--- a/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
+++ b/tests/tests/voiceinteraction/common/src/android/voiceinteraction/common/Utils.java
@@ -179,6 +179,8 @@
 
     public static final String HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT =
             "android.intent.action.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT";
+    public static final String HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT =
+            "android.intent.action.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT";
     public static final String HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT =
             "android.intent.action.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT";
     public static final String KEY_SERVICE_TYPE = "serviceType";
diff --git a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
index 337d017..ade7824 100644
--- a/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
+++ b/tests/tests/voiceinteraction/service/src/android/voiceinteraction/service/BasicVoiceInteractionService.java
@@ -214,8 +214,13 @@
                         @Override
                         public void onHotwordDetectionServiceInitialized(int status) {
                             super.onHotwordDetectionServiceInitialized(status);
-                            Log.i(TAG, "onHotwordDetectionServiceInitialized");
-                            verifyHotwordDetectionServiceInitializedStatus(status);
+                            Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
+                            if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
+                                return;
+                            }
+                            broadcastIntentWithResult(
+                                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
                         }
 
                         @Override
@@ -257,7 +262,7 @@
                         public void onError() {
                             Log.i(TAG, "onError");
                             broadcastIntentWithResult(
-                                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                                    Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
                                     Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR);
                         }
 
@@ -278,7 +283,13 @@
 
                         @Override
                         public void onHotwordDetectionServiceInitialized(int status) {
-                            verifyHotwordDetectionServiceInitializedStatus(status);
+                            Log.i(TAG, "onHotwordDetectionServiceInitialized status = " + status);
+                            if (status != HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
+                                return;
+                            }
+                            broadcastIntentWithResult(
+                                    Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+                                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
                         }
 
                         @Override
@@ -363,12 +374,4 @@
             mTempParcelFileDescriptor = null;
         }
     }
-
-    private void verifyHotwordDetectionServiceInitializedStatus(int status) {
-        if (status == HotwordDetectionService.INITIALIZATION_STATUS_SUCCESS) {
-            broadcastIntentWithResult(
-                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
-                    Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
-        }
-    }
 }
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index d25632a..98d8e67 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -124,6 +124,41 @@
     }
 
     @Test
+    @RequiresDevice
+    public void testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess()
+            throws Throwable {
+        Thread.sleep(CLEAR_CHIP_MS);
+        final BlockingBroadcastReceiver softwareReceiver = new BlockingBroadcastReceiver(mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT);
+        final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT);
+        softwareReceiver.register();
+        receiver.register();
+
+        // Create SoftwareHotwordDetector
+        perform(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST);
+        // Create AlwaysOnHotwordDetector
+        perform(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST);
+
+        final Intent softwareIntent = softwareReceiver.awaitForBroadcast(TIMEOUT_MS);
+        softwareReceiver.unregisterQuietly();
+
+        assertThat(softwareIntent).isNull();
+
+        final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
+        receiver.unregisterQuietly();
+
+        assertThat(intent).isNotNull();
+        assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(
+                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+
+        verifyDetectedResult(
+                performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+                MainHotwordDetectionService.DETECTED_RESULT);
+        verifyMicrophoneChip(true);
+    }
+
+    @Test
     public void testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure()
             throws Throwable {
         testHotwordDetection(Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
@@ -193,7 +228,7 @@
         Thread.sleep(CLEAR_CHIP_MS);
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
         testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
 
         verifyDetectedResult(
@@ -208,7 +243,7 @@
             throws Throwable {
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
         testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
 
         // The HotwordDetectionService can't report any result after recognition is stopped. So
@@ -228,7 +263,7 @@
     public void testHotwordDetectionService_concurrentCapture() throws Throwable {
         // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
         testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
-                Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+                Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
                 Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
 
         SystemUtil.runWithShellPermissionIdentity(() -> {
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
index bc43563..328931b 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/VoiceInteractionTest.java
@@ -30,6 +30,8 @@
 import android.util.Log;
 import android.voiceinteraction.common.Utils;
 
+import androidx.test.rule.ActivityTestRule;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -38,8 +40,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import androidx.test.rule.ActivityTestRule;
-
 // TODO: ideally we should split testAll() into multiple tests, and run at least one of them
 // on instant
 @AppModeFull(reason = "DirectActionsTest is enough")
@@ -59,7 +59,8 @@
     @Before
     public void setUp() throws Exception {
         mReceiver = new TestResultsReceiver();
-        mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT));
+        mContext.registerReceiver(mReceiver, new IntentFilter(Utils.BROADCAST_INTENT),
+                Context.RECEIVER_EXPORTED);
         startTestActivity();
     }
 
diff --git a/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java b/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
index b734b47..328980a0 100644
--- a/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
+++ b/tests/tests/voicesettings/src/android/voicesettings/cts/BroadcastTestBase.java
@@ -130,7 +130,8 @@
         mLatch = new CountDownLatch(1);
         mActivityDoneReceiver = new ActivityDoneReceiver();
         mContext.registerReceiver(mActivityDoneReceiver,
-                new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()));
+                new IntentFilter(BroadcastUtils.BROADCAST_INTENT + testCaseType.toString()),
+                Context.RECEIVER_EXPORTED);
     }
 
     protected boolean startTestAndWaitForBroadcast(BroadcastUtils.TestcaseType testCaseType,
diff --git a/tests/tests/webkit/Android.bp b/tests/tests/webkit/Android.bp
index 30cb45f..d8175dd 100644
--- a/tests/tests/webkit/Android.bp
+++ b/tests/tests/webkit/Android.bp
@@ -31,7 +31,10 @@
         "ctstestrunner-axt",
         "hamcrest-library",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.aidl",
+    ],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
index 65cc8e6..4e390c9 100644
--- a/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/CookieManagerTest.java
@@ -172,7 +172,7 @@
         final String url = "http://www.example.com";
         final String cookie = "name=test";
         mCookieManager.setCookie(url, cookie, null);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 String c = mCookieManager.getCookie(url);
@@ -255,7 +255,7 @@
 
         mCookieManager.removeSessionCookies(null);
         allCookies = mCookieManager.getCookie(url);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 String c = mCookieManager.getCookie(url);
@@ -266,7 +266,7 @@
         }.run();
 
         mCookieManager.removeAllCookies(null);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return !mCookieManager.hasCookies();
@@ -452,7 +452,7 @@
     }
 
     private void waitForCookie(final String url) {
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mCookieManager.getCookie(url) != null;
diff --git a/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl b/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl
new file mode 100644
index 0000000..173c644
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/ITestProcessService.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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.webkit.cts;
+
+import android.os.Bundle;
+
+interface ITestProcessService {
+    /**
+     * Runs the given test class.
+     *
+     * <p>This is a sync call.
+     *
+     * @param testClassName the name of a test class that extends {@code
+     *     TestProcessClient#TestRunnable}.
+     * @return test result as a bundle. If the test passes, the bundle will be empty. If it fails,
+     *     it will contain the failure exception as a Serializable.
+     */
+    Bundle run(String testClassName);
+
+    /**
+     * Terminates the TestProcessService process.
+     *
+     * <p>This is a sync call.
+     */
+    void exit();
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
index 5204f56..ee6f11f 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PacProcessorTest.java
@@ -23,6 +23,7 @@
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
+import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
@@ -37,6 +38,11 @@
         mProcess = TestProcessClient.createProcessB(context);
     }
 
+    @After
+    public void tearDown() throws Throwable {
+        mProcess.close();
+    }
+
     static class TestCreatePacProcessor extends TestProcessClient.TestRunnable {
         @Override
         public void run(Context ctx) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
index c2fff9e..da5664e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/PostMessageTest.java
@@ -35,8 +35,6 @@
 import java.util.concurrent.BlockingQueue;
 
 public class PostMessageTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-    public static final long TIMEOUT = 20000L;
-
     private WebView mWebView;
     private WebViewOnUiThread mOnUiThread;
 
@@ -96,7 +94,7 @@
     }
 
     private void waitForTitle(final String title) {
-        new PollingCheck(TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getTitle().equals(title);
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
index ba6ae47..0a3946e 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClient.java
@@ -20,11 +20,11 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
 import android.os.RemoteException;
 
 import com.android.internal.annotations.GuardedBy;
@@ -32,24 +32,30 @@
 import com.google.common.util.concurrent.SettableFuture;
 
 import junit.framework.Assert;
-import junit.framework.AssertionFailedError;
 
+/**
+ * IPC interface to run tests in a freshly spawned service process.
+ *
+ * CTS test modules usually run all tests in the same process, but some WebView tests
+ * need to verify things that only happen once per process. This client interface allows
+ * two separate service processes to be created which are guaranteed to be freshly launched
+ * and to not have loaded the WebView implementation before the test runs. The caller must
+ * close() the client once it's done with it to allow the service process to exit.
+ * The two service processes are identical to each other (A and B are arbitrary labels); we
+ * have two in case a test needs to run more than one thing at once.
+ */
 class TestProcessClient extends Assert implements AutoCloseable, ServiceConnection {
     private Context mContext;
 
-    static final long REMOTE_TIMEOUT_MS = 5000;
-
     private static final long CONNECT_TIMEOUT_MS = 5000;
 
     private Object mLock = new Object();
-    @GuardedBy("mLock")
-    private Messenger mService;
-    @GuardedBy("mLock")
-    private Integer mLastResult;
-    @GuardedBy("mLock")
-    private Throwable mLastException;
 
-    private final Messenger mReplyHandler = new Messenger(new ReplyHandler(Looper.getMainLooper()));
+    @GuardedBy("mLock")
+    private ITestProcessService mService;
+
+    @GuardedBy("mLock")
+    private boolean mIsConnectionClosed = false;
 
     public static TestProcessClient createProcessA(Context context) throws Throwable {
         return new TestProcessClient(context, TestProcessServiceA.class);
@@ -59,22 +65,18 @@
         return new TestProcessClient(context, TestProcessServiceB.class);
     }
 
-    /**
-     * Subclass this to implement test code to run on the service side.
-     */
-    static abstract class TestRunnable extends Assert {
+    /** Subclass this to implement test code to run on the service side. */
+    abstract static class TestRunnable extends Assert {
         public abstract void run(Context ctx) throws Throwable;
     }
 
-    /**
-     * Subclass this to implement test code that runs on the main looper on the service side.
-     */
-    static abstract class UiThreadTestRunnable extends TestRunnable {
+    /** Subclass this to implement test code that runs on the main looper on the service side. */
+    abstract static class UiThreadTestRunnable extends TestRunnable {
         // A handler for the main thread.
         private static final Handler sMainThreadHandler = new Handler(Looper.getMainLooper());
 
         @Override
-        final public void run(Context ctx) throws Throwable {
+        public final void run(Context ctx) throws Throwable {
             final SettableFuture<Void> exceptionPropagatingFuture = SettableFuture.create();
             sMainThreadHandler.post(new Runnable() {
                 @Override
@@ -95,6 +97,7 @@
 
     static class ProcessFreshChecker extends TestRunnable {
         private static Object sFreshLock = new Object();
+
         @GuardedBy("sFreshLock")
         private static boolean sFreshProcess = true;
 
@@ -104,10 +107,9 @@
                 if (!sFreshProcess) {
                     fail("Service process was unexpectedly reused");
                 }
-                sFreshProcess = true;
+                sFreshProcess = false;
             }
         }
-
     }
 
     private TestProcessClient(Context context, Class service) throws Throwable {
@@ -124,48 +126,35 @@
         }
 
         // Check that we're using an actual fresh process.
-        // 1000ms timeout is plenty since the service is already running.
-        run(ProcessFreshChecker.class, 1000);
+        run(ProcessFreshChecker.class);
     }
 
     public void run(Class runnableClass) throws Throwable {
-        run(runnableClass, REMOTE_TIMEOUT_MS);
-    }
-
-    public void run(Class runnableClass, long timeoutMs) throws Throwable {
-        Message m = Message.obtain(null, TestProcessService.MSG_RUN_TEST);
-        m.replyTo = mReplyHandler;
-        m.getData().putString(TestProcessService.TEST_CLASS_KEY, runnableClass.getName());
-        int result;
-        Throwable exception;
+        Bundle result;
         synchronized (mLock) {
-            mService.send(m);
-            if (mLastResult == null) {
-                mLock.wait(timeoutMs);
-                if (mLastResult == null) {
-                    fail("Timeout waiting for result");
-                }
-            }
-            result = mLastResult;
-            mLastResult = null;
-            exception = mLastException;
-            mLastException = null;
+            result = mService.run(runnableClass.getName());
         }
-        if (result == TestProcessService.REPLY_EXCEPTION) {
+        Throwable exception =
+                (Throwable) result.getSerializable(TestProcessService.REPLY_EXCEPTION_KEY);
+        if (exception != null) {
             throw exception;
-        } else if (result != TestProcessService.REPLY_OK) {
-            fail("Unknown result from service: " + result);
         }
     }
 
     public void close() {
         synchronized (mLock) {
-            if (mService != null) {
-                try {
-                    mService.send(Message.obtain(null, TestProcessService.MSG_EXIT_PROCESS));
-                } catch (RemoteException e) {}
-                mService = null;
-                mContext.unbindService(this);
+            if (mIsConnectionClosed) {
+                return;
+            }
+            mIsConnectionClosed = true;
+            try {
+                if (mService != null) {
+                    mService.exit();
+                    fail("This should result in a DeadObjectException");
+                }
+            } catch (DeadObjectException e) {
+            } catch (RemoteException e) {
+                throw new RuntimeException(e);
             }
         }
     }
@@ -173,7 +162,7 @@
     @Override
     public void onServiceConnected(ComponentName className, IBinder service) {
         synchronized (mLock) {
-            mService = new Messenger(service);
+            mService = ITestProcessService.Stub.asInterface(service);
             mLock.notify();
         }
     }
@@ -183,26 +172,10 @@
         synchronized (mLock) {
             mService = null;
             mContext.unbindService(this);
-            mLastResult = TestProcessService.REPLY_EXCEPTION;
-            mLastException = new AssertionFailedError("Service disconnected unexpectedly");
             mLock.notify();
-        }
-    }
-
-    private class ReplyHandler extends Handler {
-        ReplyHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            synchronized (mLock) {
-                mLastResult = msg.what;
-                if (msg.what == TestProcessService.REPLY_EXCEPTION) {
-                    mLastException = (Throwable) msg.getData().getSerializable(
-                            TestProcessService.REPLY_EXCEPTION_KEY);
-                }
-                mLock.notify();
+            // Service wasn't explicitly disconnected in the close() method.
+            if (!mIsConnectionClosed) {
+                fail("Service disconnected unexpectedly");
             }
         }
     }
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java
new file mode 100644
index 0000000..f605f64
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessClientTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.webkit.cts;
+
+import android.content.Context;
+import android.os.Looper;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+import java.io.IOException;
+
+/**
+ * Test various scenarios of using {@link TestProcessService} and {@link TestProcessClient}
+ * framework to run tests cases in freshly created test processes.
+ */
+public class TestProcessClientTest extends InstrumentationTestCase {
+
+    static class TestRunningOnUiThread extends TestProcessClient.UiThreadTestRunnable {
+        @Override
+        protected void runOnUiThread(Context ctx) throws Throwable {
+            Assert.assertTrue(
+                    "Test is not running on the main thread",
+                    Looper.getMainLooper().isCurrentThread());
+        }
+    }
+
+    static class TestRunningOnDefaultThread extends TestProcessClient.TestRunnable {
+        private static Looper sLooper;
+
+        @Override
+        public void run(Context ctx) throws Throwable {
+            Assert.assertFalse(
+                    "Default thread should be different from the main thread",
+                    Looper.getMainLooper().isCurrentThread());
+            if (sLooper == null) {
+                sLooper = Looper.myLooper();
+                Assert.assertNotNull("The default thread should have a looper", sLooper);
+            } else {
+                Assert.assertTrue(
+                        "Test cases should run on the same thread", sLooper.isCurrentThread());
+            }
+        }
+    }
+
+    public void testRunDifferentRunnables() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestRunningOnUiThread.class);
+            process.run(TestRunningOnDefaultThread.class);
+            process.run(TestRunningOnDefaultThread.class);
+        }
+    }
+
+    static class TestNullPointerException extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) throws Throwable {
+            throw new NullPointerException("Test NullPointerException, should be caught");
+        }
+    }
+
+    /**
+     * Test throwing an exception that is handled by the Parcel class: {@link
+     * Parcel#writeException(java.lang.Exception)}.
+     */
+    public void testThrowingNullPointerException() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestNullPointerException.class);
+            fail("A NullPointerException is expected to be thrown");
+        } catch (NullPointerException e) {
+
+        }
+    }
+
+    static class TestIOException extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) throws Throwable {
+            throw new IOException("Test IOException, should be caught");
+        }
+    }
+
+    /**
+     * Test throwing an exception that is not handled by the Parcel class: {@link
+     * Parcel#writeException(java.lang.Exception)}.
+     */
+    public void testThrowingIOException() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestIOException.class);
+            fail("An IOException is expected to be thrown");
+        } catch (IOException e) {
+        }
+    }
+
+    static class TestFailedAssertion extends TestProcessClient.TestRunnable {
+        @Override
+        public void run(Context ctx) throws Throwable {
+            fail("This assertion should be caught");
+        }
+    }
+
+    /**
+     * Test that junit assertions failures are propagated as expected.
+     */
+    public void testFailedAssertion() throws Throwable {
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        try (TestProcessClient process = TestProcessClient.createProcessA(context)) {
+            process.run(TestFailedAssertion.class);
+            fail("An AssertionFailedError is expected to be thrown");
+        } catch (AssertionFailedError e) {
+            Assert.assertEquals("This assertion should be caught", e.getMessage());
+        }
+    }
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
index 499d18c..f3c80b2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
+++ b/tests/tests/webkit/src/android/webkit/cts/TestProcessService.java
@@ -17,79 +17,53 @@
 package android.webkit.cts;
 
 import android.app.Service;
-import android.content.Context;
 import android.content.Intent;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
 
-import junit.framework.Assert;
-import junit.framework.AssertionFailedError;
+import com.google.common.util.concurrent.SettableFuture;
 
 // Subclasses are the ones that get actually used, so make this abstract
 abstract class TestProcessService extends Service {
-    static final int MSG_RUN_TEST = 0;
-    static final int MSG_EXIT_PROCESS = 1;
-    static final String TEST_CLASS_KEY = "class";
-
-    static final int REPLY_OK = 0;
-    static final int REPLY_EXCEPTION = 1;
     static final String REPLY_EXCEPTION_KEY = "exception";
 
-    @Override
-    public IBinder onBind(Intent intent) {
-        return mMessenger.getBinder();
-    }
-
-    final Messenger mMessenger;
+    private final Handler mHandler;
 
     public TestProcessService() {
-        HandlerThread backgroundThread = new HandlerThread("TestThread");
-        backgroundThread.start();
-        mMessenger = new Messenger(new IncomingHandler(backgroundThread.getLooper()));
+        HandlerThread handlerThread = new HandlerThread("TestThread");
+        handlerThread.start();
+        mHandler = new Handler(handlerThread.getLooper());
     }
 
-    private class IncomingHandler extends Handler {
-        IncomingHandler(Looper looper) {
-            super(looper);
+    private final ITestProcessService.Stub mBinder = new ITestProcessService.Stub() {
+        @Override
+        public Bundle run(String testClassName) {
+            final SettableFuture<Bundle> testResultFuture = SettableFuture.create();
+            mHandler.post(() -> {
+                Bundle testResultBundle = new Bundle();
+                try {
+                    Class testClass = Class.forName(testClassName);
+                    TestProcessClient.TestRunnable test =
+                            (TestProcessClient.TestRunnable) testClass.newInstance();
+                    test.run(TestProcessService.this);
+                } catch (Throwable t) {
+                    testResultBundle.putSerializable(REPLY_EXCEPTION_KEY, t);
+                }
+                testResultFuture.set(testResultBundle);
+            });
+            return WebkitUtils.waitForFuture(testResultFuture);
         }
 
         @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == MSG_EXIT_PROCESS) {
-                System.exit(0);
-            }
-
-            try {
-                if (msg.what != MSG_RUN_TEST) {
-                    throw new AssertionFailedError("Unknown service message " + msg.what);
-                }
-
-                String testClassName = msg.getData().getString(TEST_CLASS_KEY);
-                Class testClass = Class.forName(testClassName);
-                TestProcessClient.TestRunnable test =
-                        (TestProcessClient.TestRunnable) testClass.newInstance();
-                test.run(TestProcessService.this);
-            } catch (Throwable t) {
-                try {
-                    Message m = Message.obtain(null, REPLY_EXCEPTION);
-                    m.getData().putSerializable(REPLY_EXCEPTION_KEY, t);
-                    msg.replyTo.send(m);
-                } catch (RemoteException e) {
-                    throw new RuntimeException(e);
-                }
-                return;
-            }
-
-            try {
-                msg.replyTo.send(Message.obtain(null, REPLY_OK));
-            } catch (RemoteException e) {
-                throw new RuntimeException(e);
-            }
+        public void exit() {
+            System.exit(0);
         }
+    };
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
     }
 }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
index bd81019..b40e672 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebBackForwardListTest.java
@@ -27,8 +27,6 @@
 
 public class WebBackForwardListTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
 
-    private static final int TEST_TIMEOUT = 10000;
-
     private WebViewOnUiThread mOnUiThread;
 
     public WebBackForwardListTest() {
@@ -85,7 +83,7 @@
     }
 
     private void checkBackForwardList(final String... url) {
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 if (mOnUiThread.getProgress() < 100) {
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
index dbfcfa2..9be1561 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebChromeClientTest.java
@@ -44,7 +44,6 @@
 
 @AppModeFull
 public class WebChromeClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-    private static final long TEST_TIMEOUT = 5000L;
     private static final String JAVASCRIPT_UNLOAD = "javascript unload";
     private static final String LISTENER_ADDED = "listener added";
     private static final String TOUCH_RECEIVED = "touch received";
@@ -95,7 +94,7 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnProgressChanged();
@@ -114,7 +113,7 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnReceivedTitle();
@@ -146,7 +145,7 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnReceivedIcon();
@@ -171,7 +170,7 @@
         mOnUiThread.loadUrlAndWaitForCompletion(mWebServer.
                 getAssetUrl(TestHtmlConstants.JS_WINDOW_URL));
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnCreateWindow();
@@ -179,7 +178,7 @@
         }.run();
 
         if (expectWindowClose) {
-            new PollingCheck(TEST_TIMEOUT) {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                 protected boolean check() {
                     return webChromeClient.hadOnCloseWindow();
@@ -273,7 +272,7 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_ALERT_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnJsAlert();
@@ -298,7 +297,7 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_CONFIRM_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnJsConfirm();
@@ -325,14 +324,14 @@
         String url = mWebServer.getAssetUrl(TestHtmlConstants.JS_PROMPT_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webChromeClient.hadOnJsPrompt();
             }
         }.run();
         // the result returned by the client gets set as the page title
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getTitle().equals(promptResult);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
index cae5ff8..45529ec 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebHistoryItemTest.java
@@ -30,7 +30,6 @@
 
 @AppModeFull
 public class WebHistoryItemTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-    private final static long TEST_TIMEOUT = 10000;
     private CtsTestServer mWebServer;
     private WebViewOnUiThread mOnUiThread;
     private WebIconDatabase mIconDb;
@@ -95,7 +94,7 @@
 
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return waitForIconClient.receivedIcon();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 513db9b..02a0b22 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -67,8 +67,6 @@
  */
 @AppModeFull
 public class WebSettingsTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-
-    private static final int WEBVIEW_TIMEOUT = 5000;
     private static final String LOG_TAG = "WebSettingsTest";
 
     private final String EMPTY_IMAGE_HEIGHT = "0";
@@ -515,7 +513,7 @@
         mSettings.setJavaScriptCanOpenWindowsAutomatically(false);
         assertFalse(mSettings.getJavaScriptCanOpenWindowsAutomatically());
         mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.POPUP_URL));
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return "Popup blocked".equals(mOnUiThread.getTitle());
@@ -536,7 +534,7 @@
         mSettings.setJavaScriptEnabled(true);
         assertTrue(mSettings.getJavaScriptEnabled());
         loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return "javascript on".equals(mOnUiThread.getTitle());
@@ -546,7 +544,7 @@
         mSettings.setJavaScriptEnabled(false);
         assertFalse(mSettings.getJavaScriptEnabled());
         loadAssetUrl(TestHtmlConstants.JAVASCRIPT_URL);
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return "javascript off".equals(mOnUiThread.getTitle());
@@ -737,7 +735,7 @@
         mSettings.setJavaScriptEnabled(true);
 
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             protected boolean check() {
                 return "Loaded".equals(mOnUiThread.getTitle());
             }
@@ -764,7 +762,7 @@
         mSettings.setJavaScriptEnabled(true);
 
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return "Loaded".equals(mOnUiThread.getTitle());
@@ -1297,7 +1295,7 @@
     }
 
     private void waitForNonEmptyImage() {
-        new PollingCheck(WEBVIEW_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return !EMPTY_IMAGE_HEIGHT.equals(mOnUiThread.getTitle());
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index 0161540..dd10626 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -51,7 +51,6 @@
 
 @AppModeFull
 public class WebViewClientTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-    private static final long TEST_TIMEOUT = 5000;
     private static final String TEST_URL = "http://www.example.com/";
 
     private WebViewOnUiThread mOnUiThread;
@@ -78,7 +77,7 @@
         final WebViewCtsActivity activity = getActivity();
         WebView webview = activity.getWebView();
         if (webview != null) {
-            new PollingCheck(TEST_TIMEOUT) {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                     protected boolean check() {
                     return activity.hasWindowFocus();
@@ -178,13 +177,13 @@
           final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
           mOnUiThread.loadUrl(mWebServer.getAssetUrl(TestHtmlConstants.BLANK_TAG_URL));
 
-          new PollingCheck(TEST_TIMEOUT) {
+          new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
               @Override
               protected boolean check() {
                   return childWebViewClient.hasOnPageFinishedCalled();
               }
           }.run();
-          new PollingCheck(TEST_TIMEOUT) {
+          new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
               @Override
               protected boolean check() {
                   return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
@@ -197,7 +196,7 @@
         final int childCallCount = childWebViewClient.getShouldOverrideUrlLoadingCallCount();
         final int mainCallCount = mainWebViewClient.getShouldOverrideUrlLoadingCallCount();
         clickOnLinkUsingJs("link", childWebViewOnUiThread);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return childWebViewClient.getShouldOverrideUrlLoadingCallCount() > childCallCount;
@@ -230,21 +229,21 @@
         assertFalse(webViewClient.hasOnPageFinishedCalled());
         mOnUiThread.loadUrlAndWaitForCompletion(url);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnPageStartedCalled();
             }
         }.run();
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnLoadResourceCalled();
             }
         }.run();
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnPageFinishedCalled();
@@ -273,7 +272,7 @@
             assertFalse(webViewClient.hasOnReceivedLoginRequest());
             mOnUiThread.loadUrlAndWaitForCompletion(url);
             assertTrue(webViewClient.hasOnReceivedLoginRequest());
-            new PollingCheck(TEST_TIMEOUT) {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                 protected boolean check() {
                     return webViewClient.hasOnReceivedLoginRequest();
@@ -361,7 +360,7 @@
                 url.equals(mOnUiThread.getUrl()));
         // reloading the current URL should trigger the callback
         mOnUiThread.reload();
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnFormResubmissionCalled();
@@ -382,7 +381,7 @@
         String url2 = mWebServer.getAssetUrl(TestHtmlConstants.BR_TAG_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url1);
         mOnUiThread.loadUrlAndWaitForCompletion(url2);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasDoUpdateVisitedHistoryCalled();
@@ -428,7 +427,7 @@
         assertFalse(webViewClient.hasOnUnhandledKeyEventCalled());
         sendKeys(KeyEvent.KEYCODE_1);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnUnhandledKeyEventCalled();
@@ -448,7 +447,7 @@
         String url1 = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(url1);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.canZoomIn();
@@ -456,7 +455,7 @@
         }.run();
 
         assertTrue(mOnUiThread.zoomIn());
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasOnScaleChangedCalled();
@@ -618,7 +617,7 @@
         final MockWebViewClient webViewClient = new MockWebViewClient();
         mOnUiThread.setWebViewClient(webViewClient);
         mOnUiThread.loadUrl("chrome://kill");
-        new PollingCheck(TEST_TIMEOUT * 5) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return webViewClient.hasRenderProcessGoneCalled();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index f9496ab..1796464 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -446,7 +446,7 @@
         final WebViewCtsActivity activity = getActivity();
         mWebView = activity.getWebView();
         if (mWebView != null) {
-            new PollingCheck() {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                     protected boolean check() {
                         return activity.hasWindowFocus();
@@ -561,21 +561,24 @@
         mOnUiThread.setWebViewClient(webViewClient);
         mOnUiThread.clearSslPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertTrue("onReceivedSslError should be called",
+                webViewClient.wasOnReceivedSslErrorCalled());
 
         // Load the page again. We expect another call to
         // WebViewClient.onReceivedSslError() since we cleared sslpreferences.
         mOnUiThread.clearSslPreferences();
         webViewClient.resetCallCounts();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertTrue("onReceivedSslError should be called again after clearing SSL preferences",
+                webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
 
         // Load the page once again, without clearing the sslpreferences.
         // Make sure we do not get the callback.
         webViewClient.resetCallCounts();
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+        assertFalse("onReceivedSslError should not be called when SSL preferences are not cleared",
+                webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals(TestHtmlConstants.HELLO_WORLD_TITLE, mOnUiThread.getTitle());
     }
 
@@ -670,14 +673,16 @@
         mOnUiThread.setWebViewClient(webViewClient);
         mOnUiThread.clearSslPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(firstUrl);
-        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertTrue("onReceivedSslError should be called on loading first page",
+                webViewClient.wasOnReceivedSslErrorCalled());
 
         // Load the second page. We don't expect a call to
         // WebViewClient.onReceivedSslError(), but the page should load.
         webViewClient.resetCallCounts();
         final String sameHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2);
         mOnUiThread.loadUrlAndWaitForCompletion(sameHostUrl);
-        assertFalse(webViewClient.wasOnReceivedSslErrorCalled());
+        assertFalse("onReceivedSslError should not be called on loading second page",
+                webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals("Second page", mOnUiThread.getTitle());
     }
 
@@ -693,7 +698,8 @@
         mOnUiThread.setWebViewClient(webViewClient);
         mOnUiThread.clearSslPreferences();
         mOnUiThread.loadUrlAndWaitForCompletion(firstUrl);
-        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertTrue("onReceivedSslError should be called when request is sent to localhost",
+                webViewClient.wasOnReceivedSslErrorCalled());
 
         // Load the second page. We expect another call to
         // WebViewClient.onReceivedSslError().
@@ -703,7 +709,8 @@
         final String differentHostUrl = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL2).replace(
                 "localhost", "127.0.0.1");
         mOnUiThread.loadUrlAndWaitForCompletion(differentHostUrl);
-        assertTrue(webViewClient.wasOnReceivedSslErrorCalled());
+        assertTrue("onReceivedSslError should be called when request is sent to 127.0.0.1",
+                webViewClient.wasOnReceivedSslErrorCalled());
         assertEquals("Second page", mOnUiThread.getTitle());
     }
 
@@ -955,7 +962,7 @@
         });
         // Wait until clearclientcertpreferences clears the preferences. Generally this is just a
         // thread hopping.
-        new PollingCheck(WebViewTest.TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return cleared.get();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index e5a0041..abe44ea 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -122,7 +122,6 @@
 
 @AppModeFull
 public class WebViewTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
-    public static final long TEST_TIMEOUT = 20000L;
     private static final int INITIAL_PROGRESS = 100;
     private static final String X_REQUESTED_WITH = "X-Requested-With";
     private static final String PRINTER_TEST_FILE = "print.pdf";
@@ -169,7 +168,7 @@
         final WebViewCtsActivity activity = getActivity();
         mWebView = activity.getWebView();
         if (mWebView != null) {
-            new PollingCheck() {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                     protected boolean check() {
                         return activity.hasWindowFocus();
@@ -608,7 +607,7 @@
             private synchronized String waitForResult() {
                 while (!mWasProvideResultCalled) {
                     try {
-                        wait(TEST_TIMEOUT);
+                        wait(WebkitUtils.TEST_TIMEOUT_MS);
                     } catch (InterruptedException e) {
                         continue;
                     }
@@ -883,7 +882,7 @@
             });
             if (isPictureFilledWithColor(pictureRef.get(), color))
                 break;
-            new PollingCheck(TEST_TIMEOUT) {
+            new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
                 @Override
                 protected boolean check() {
                     return listener.callCount > oldCallCount;
@@ -942,7 +941,7 @@
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         mOnUiThread.setPictureListener(listener);
         mOnUiThread.loadUrlAndWaitForCompletion(url);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return listener.callCount > 0;
@@ -954,7 +953,7 @@
         final int oldCallCount = listener.callCount;
         final String newUrl = mWebServer.getAssetUrl(TestHtmlConstants.SMALL_IMG_URL);
         mOnUiThread.loadUrlAndWaitForCompletion(newUrl);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return listener.callCount > oldCallCount;
@@ -1264,7 +1263,7 @@
         };
 
         mOnUiThread.saveWebArchive(baseName, autoName, callback);
-        assertTrue(saving.tryAcquire(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+        assertTrue(saving.tryAcquire(WebkitUtils.TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS));
     }
 
     public void testSaveWebArchive() throws Throwable {
@@ -1532,7 +1531,7 @@
 
         // Wait for UI thread to settle and receive page dimentions from renderer
         // such that we can invoke page down.
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                  return mOnUiThread.pageDown(false);
@@ -1557,7 +1556,7 @@
 
         // jump to the bottom
         assertTrue(mOnUiThread.pageDown(true));
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return bottomScrollY == mOnUiThread.getScrollY();
@@ -1566,7 +1565,7 @@
 
         // jump to the top
         assertTrue(mOnUiThread.pageUp(true));
-         new PollingCheck() {
+         new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return topScrollY == mOnUiThread.getScrollY();
@@ -1580,7 +1579,7 @@
         }
         mOnUiThread.loadDataAndWaitForCompletion(
                 "<html><body></body></html>", "text/html", null);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getScale() != 0 && mOnUiThread.getContentHeight() != 0
@@ -1620,7 +1619,7 @@
                 + "px;margin:0px auto;\">Get the height of HTML content.</p>";
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "</body></html>", "text/html", null);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getContentHeight() > pageHeight;
@@ -1630,7 +1629,7 @@
         final int extraSpace = mOnUiThread.getContentHeight() - pageHeight;
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + p + "</body></html>", "text/html", null);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 // |pageHeight| is accurate, |extraSpace| = getContentheight() - |pageHeight|, so it
@@ -1684,7 +1683,7 @@
                 "width:" + dimension + "px\">Test fling scroll.</p>";
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "</body></html>", "text/html", null);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getContentHeight() >= dimension;
@@ -1697,7 +1696,7 @@
 
         mOnUiThread.flingScroll(10000, 10000);
 
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getScrollX() > previousScrollX &&
@@ -1727,7 +1726,7 @@
         handler.reset();
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
         mOnUiThread.requestFocusNodeHref(hrefMsg);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 boolean done = false;
@@ -1752,7 +1751,7 @@
         hrefMsg2.setTarget(handler);
         getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
         mOnUiThread.requestFocusNodeHref(hrefMsg2);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 boolean done = false;
@@ -1830,7 +1829,7 @@
                         middleX, middleY, 0));
         getInstrumentation().waitForIdleSync();
         mOnUiThread.requestImageRef(msg);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 boolean done = false;
@@ -1933,7 +1932,7 @@
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "</body></html>", "text/html", null);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -1945,7 +1944,7 @@
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "2" + "</body></html>", "text/html", null);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -1956,7 +1955,7 @@
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "3" + "</body></html>", "text/html", null);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return Math.abs(0.5 - mOnUiThread.getScale()) < .01f;
@@ -1967,7 +1966,7 @@
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "4" + "</body></html>", "text/html", null);
 
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return Math.abs(defaultScale - mOnUiThread.getScale()) < .01f;
@@ -2045,7 +2044,7 @@
         assertEquals(2, saveList.getCurrentIndex());
 
         // wait for the list items to get inflated
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return restoreList.getItemAtIndex(0).getUrl() != null &&
@@ -2081,7 +2080,7 @@
         String p = "<p style=\"height:" + dimension + "px;width:" + dimension + "px\">&nbsp;</p>";
         mOnUiThread.loadDataAndWaitForCompletion("<html><body>" + p
                 + "</body></html>", "text/html", null);
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getContentHeight() >= dimension;
@@ -2180,7 +2179,7 @@
         mOnUiThread.setNetworkAvailable(false);
 
         // Wait for the DOM to receive notification of the network state change.
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getTitle().equals("OFFLINE");
@@ -2190,7 +2189,7 @@
         mOnUiThread.setNetworkAvailable(true);
 
         // Wait for the DOM to receive notification of the network state change.
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getTitle().equals("ONLINE");
@@ -2317,7 +2316,7 @@
 
         final String EXPECTED_TITLE = "test";
         mOnUiThread.evaluateJavascript("document.title='" + EXPECTED_TITLE + "';", null);
-        new PollingCheck(TEST_TIMEOUT) {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 return mOnUiThread.getTitle().equals(EXPECTED_TITLE);
@@ -2732,7 +2731,7 @@
 
     private void pollingCheckWebBackForwardList(final String currUrl, final int currIndex,
             final int size) {
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
             protected boolean check() {
                 WebBackForwardList list = mWebView.copyBackForwardList();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
index 2f247f2..dc160b2 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewZoomTest.java
@@ -69,7 +69,7 @@
         mOnUiThread = new WebViewOnUiThread(mWebView);
         mOnUiThread.requestFocus();
 
-        new PollingCheck() {
+        new PollingCheck(WebkitUtils.TEST_TIMEOUT_MS) {
             @Override
                 protected boolean check() {
                     return activity.hasWindowFocus();
@@ -108,7 +108,8 @@
     }
 
     private void setUpPage() throws Exception {
-        assertFalse(mWebViewClient.onScaleChangedCalled());
+        assertFalse("onScaleChanged has already been called before page has been setup",
+                mWebViewClient.onScaleChangedCalled());
         assertNull(mWebServer);
         // Pass CtsTestserver.SslMode.TRUST_ANY_CLIENT to make the server serve https URLs yet do
         // not ask client for client authentication.
@@ -261,11 +262,14 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        assertFalse(mWebViewClient.onScaleChangedCalled());
+        assertFalse("There is an onScaleChanged before we call setUpPage()",
+                mWebViewClient.onScaleChangedCalled());
 
         setUpPage();
 
-        assertFalse(mWebViewClient.onScaleChangedCalled());
+        assertFalse("There is an onScaleChanged left over from setUpPage()",
+                mWebViewClient.onScaleChangedCalled());
+
         assertTrue(mOnUiThread.zoomIn());
         ScaleChangedState state = mWebViewClient.waitForNextScaleChange();
         assertEquals(
@@ -278,7 +282,8 @@
         // that a scale change does *not* happen.
         try {
             Thread.sleep(500);
-            assertFalse(mWebViewClient.onScaleChangedCalled());
+            assertFalse("There is an onScaleChanged left over from previous scale change",
+                    mWebViewClient.onScaleChangedCalled());
             assertEquals(currScale, mOnUiThread.getScale());
         } catch (InterruptedException e) {
             fail("Interrupted");
diff --git a/tests/tests/widget/OWNERS b/tests/tests/widget/OWNERS
index 12f176d..2f13877 100644
--- a/tests/tests/widget/OWNERS
+++ b/tests/tests/widget/OWNERS
@@ -1,5 +1,3 @@
 # Bug component: 25700
 adamp@google.com
 mount@google.com
-shepshapard@google.com
-clarabayarri@google.com
diff --git a/tests/tests/widget/res/layout/edittext_layout.xml b/tests/tests/widget/res/layout/edittext_layout.xml
index 7157d92..5be5244 100644
--- a/tests/tests/widget/res/layout/edittext_layout.xml
+++ b/tests/tests/widget/res/layout/edittext_layout.xml
@@ -55,5 +55,12 @@
             android:autoSizeTextType="uniform"
             android:textSize="50dp"
             android:autoSizeStepGranularity="2dp" />
+
+        <EditText
+            android:id="@+id/edittext_conversion_suggestion"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:inputType="textEnableTextConversionSuggestions"
+            android:text="@string/edit_text" />
     </LinearLayout>
 </ScrollView>
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 6f1c188..8f0c71f 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -453,6 +453,39 @@
                 android:lineSpacingMultiplier="0.5"
                 android:lineHeight="@dimen/textview_lineHeight" />
 
+            <TextView
+                android:id="@+id/textview_line_break_style_default"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text" />
+
+            <TextView
+                android:id="@+id/textview_line_break_style_none"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:lineBreakStyle="none" />
+
+            <TextView
+                android:id="@+id/textview_line_break_style_loose"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:lineBreakStyle="loose" />
+
+            <TextView
+                android:id="@+id/textview_line_break_style_normal"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:lineBreakStyle="normal" />
+
+            <TextView
+                android:id="@+id/textview_line_break_style_strict"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/sample_text"
+                android:lineBreakStyle="strict" />
         </LinearLayout>
 
 </ScrollView>
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
index f02f893..084aa7c 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListViewTest.java
@@ -83,6 +83,7 @@
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.hamcrest.MatcherAssert;
 import org.junit.Before;
@@ -137,7 +138,7 @@
         // Always use the activity context
         mContext = activity;
 
-        PollingCheck.waitFor(activity::hasWindowFocus);
+        WindowUtil.waitForFocus(activity);
 
         XmlPullParser parser = mContext.getResources().getXml(R.layout.listview_layout);
         WidgetTestUtils.beginDocument(parser, "FrameLayout");
@@ -427,6 +428,8 @@
         assertEquals(v.getLeft(), r.left);
         assertEquals(v.getTop(), r.top);
         assertEquals(v.getBottom(), r.bottom);
+
+
     }
 
     @Test
@@ -475,6 +478,54 @@
     }
 
     @Test
+    public void testSelectedChildViewEnabled() throws Throwable {
+        // leave touch-mode
+        mInstrumentation.setInTouchMode(false);
+        setAdapter();
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mListView, () -> {
+            mListView.requestFocus();
+            mListView.setSelectionFromTop(1, 0);
+        });
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_TAB);
+
+        final int enabledState = (new MyListView(mContext)).getEnabledStateConstant();
+        final Drawable d = mListView.getSelector();
+        assertTrue(d.isStateful());
+        int[] state;
+        boolean enabledFound;
+
+        assertTrue(mListView.isSelectedChildViewEnabled());
+
+        // If selectedChildViewEnabled is false, then the selector shouldn't contain ENABLED state.
+        mListView.setSelectedChildViewEnabled(false);
+        assertFalse(mListView.isSelectedChildViewEnabled());
+        mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState());
+        state = d.getState();
+        enabledFound = false;
+        for (int i = state.length - 1; i >= 0; i--) {
+            if (state[i] == enabledState) {
+                enabledFound = true;
+                break;
+            }
+        }
+        assertFalse(enabledFound);
+
+        // If selectedChildViewEnabled is true, then the selector should contain ENABLED state.
+        mListView.setSelectedChildViewEnabled(true);
+        assertTrue(mListView.isSelectedChildViewEnabled());
+        mActivityRule.runOnUiThread(() -> mListView.refreshDrawableState());
+        state = d.getState();
+        enabledFound = false;
+        for (int i = state.length - 1; i >= 0; i--) {
+            if (state[i] == enabledState) {
+                enabledFound = true;
+                break;
+            }
+        }
+        assertTrue(enabledFound);
+    }
+
+    @Test
     public void testSetScrollIndicators() throws Throwable {
         final Activity activity = mActivityRule.getActivity();
         TextView tv1 = (TextView) activity.findViewById(R.id.headerview1);
@@ -683,7 +734,9 @@
 
         final AbsListView.OnItemLongClickListener mockOnItemLongClickListener =
                 mock(AbsListView.OnItemLongClickListener.class);
-        listView.setOnItemLongClickListener(mockOnItemLongClickListener);
+        mActivityRule.runOnUiThread(
+                () -> listView.setOnItemLongClickListener(mockOnItemLongClickListener)
+        );
 
         verifyZeroInteractions(mockOnItemLongClickListener);
 
@@ -1220,15 +1273,20 @@
             mActivityRule.getActivity().setContentView(listView);
             listView.setAdapter(mCountriesAdapter);
         });
-
         View row = listView.getChildAt(0);
-        Rect r = new Rect();
-        r.set(0, listView.getHeight() - (row.getHeight() >> 1),
-                row.getWidth(), listView.getHeight() + (row.getHeight() >> 1));
 
+        // Initialize the test scrolled down by half the height of the first child.
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, listView, () -> {
+            listView.scrollListBy(row.getHeight() / 2);
+        });
         listView.resetIsOnScrollChangedCalled();
         assertFalse(listView.isOnScrollChangedCalled());
-        listView.requestChildRectangleOnScreen(row, r, true);
+
+        // Scroll the first child back completely into view (back to the top of the AbsListView).
+        Rect r = new Rect();
+        r.set(0, 0, row.getWidth(), row.getHeight());
+        mActivityRule.runOnUiThread(() -> listView.requestChildRectangleOnScreen(row, r, true));
+
         assertTrue(listView.isOnScrollChangedCalled());
     }
 
@@ -1401,5 +1459,9 @@
         public void resetIsOnScrollChangedCalled() {
             mIsOnScrollChangedCalled = false;
         }
+
+        public int getEnabledStateConstant() {
+            return ENABLED_STATE_SET[0];
+        }
     }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java b/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
index 15ba71b..d770869 100644
--- a/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AbsListView_ScrollTest.java
@@ -43,8 +43,8 @@
 
 import com.android.compatibility.common.util.CtsTouchUtils;
 import com.android.compatibility.common.util.CtsTouchUtils.EventInjectionListener;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -90,7 +90,7 @@
 
         final Activity activity = mActivityRule.getActivity();
 
-        PollingCheck.waitFor(() -> activity.hasWindowFocus());
+        WindowUtil.waitForFocus(activity);
 
         mCountriesAdapter = new ArrayAdapter<>(mContext,
                 R.layout.listitemfixed_layout, COUNTRY_LIST);
diff --git a/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
index 417025f..828b573 100644
--- a/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AutoCompleteTextViewTest.java
@@ -77,6 +77,7 @@
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import com.google.common.collect.ImmutableList;
 
@@ -145,7 +146,7 @@
     @Before
     public void setup() {
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
 
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mAutoCompleteTextView = (AutoCompleteTextView) mActivity
@@ -823,6 +824,15 @@
         assertFalse(mAutoCompleteTextView.isPopupShowing());
 
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mAutoCompleteTextView, () -> {
+            mAutoCompleteTextView.showDropDown();
+        });
+        assertTrue(mAutoCompleteTextView.isPopupShowing());
+
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+        // KeyEscape will also close the popup.
+        assertFalse(mAutoCompleteTextView.isPopupShowing());
+
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mAutoCompleteTextView, () -> {
             mAutoCompleteTextView.dismissDropDown();
             mAutoCompleteTextView.setText(STRING_TEST);
         });
diff --git a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
index 4078e74..c02a47c 100644
--- a/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/CheckedTextViewTest.java
@@ -135,9 +135,11 @@
         assertTrue(view1.isChecked());
         assertTrue(view2.isChecked());
 
-        view0.setChecked(true);
-        view1.setChecked(false);
-        view2.setChecked(false);
+        mActivityRule.runOnUiThread(() -> {
+            view0.setChecked(true);
+            view1.setChecked(false);
+            view2.setChecked(false);
+        });
         assertTrue(view0.isChecked());
         assertFalse(view1.isChecked());
         assertFalse(view2.isChecked());
diff --git a/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java b/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
index e3fe5bd..c3c70f0 100644
--- a/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/DialerFilterTest.java
@@ -46,8 +46,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -70,7 +70,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
 
         mDialerFilter = (DialerFilter) mActivity.findViewById(R.id.dialer_filter);
     }
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 5ea4bee..b5f7263 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -29,6 +29,7 @@
 import android.graphics.Point;
 import android.text.Editable;
 import android.text.InputFilter;
+import android.text.InputType;
 import android.text.Layout;
 import android.text.Spanned;
 import android.text.TextUtils;
@@ -693,4 +694,32 @@
         assertEquals(et.getFilters()[0], myFilter);
     }
 
+    @UiThreadTest
+    @Test
+    public void testInputTypeForConversionSuggestions() {
+        EditText editText = new EditText(mActivity);
+        editText.setInputType(EditorInfo.TYPE_CLASS_TEXT
+                | EditorInfo.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS);
+        editText.setText(mActivity.getResources().getText(R.string.even_more_long_text));
+
+        // The value of the input type is put into the EditorInfo parameter, and then the
+        // InputMethodManager can retrieve the value of the input type from EditorInfo.
+        EditorInfo editorInfo = new EditorInfo();
+        editText.onCreateInputConnection(editorInfo);
+
+        assertEquals(InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS,
+                        editorInfo.inputType);
+    }
+
+    @UiThreadTest
+    @Test
+    public void testAttributeTextConversionSuggestion() {
+        mActivity.setContentView(R.layout.edittext_layout);
+        TextView tv = (TextView) mActivity.findViewById(
+                R.id.edittext_conversion_suggestion);
+
+        assertEquals(InputType.TYPE_CLASS_TEXT
+                | InputType.TYPE_TEXT_FLAG_ENABLE_TEXT_CONVERSION_SUGGESTIONS, tv.getInputType());
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
index 192d4ba..590f7f6 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewBasicTest.java
@@ -35,8 +35,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -62,7 +62,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mExpandableListView = mActivity.getExpandableListView();
         mAdapter = mExpandableListView.getExpandableListAdapter();
         mListUtil = new ListUtil(mExpandableListView, mInstrumentation);
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
index 5baaeeb..ac0f633 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewTest.java
@@ -55,8 +55,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -79,7 +79,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mExpandableListView = mActivity.getExpandableListView();
     }
 
diff --git a/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java b/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
index a6e0796..d18ce75 100644
--- a/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ExpandableListViewWithHeadersTest.java
@@ -32,8 +32,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -56,7 +56,7 @@
     public void setup() {
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mExpandableListView = mActivity.getExpandableListView();
         mListUtil = new ListUtil(mExpandableListView, mInstrumentation);
     }
diff --git a/tests/tests/widget/src/android/widget/cts/GridViewTest.java b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
index 22b20a0..ab4a4f8 100644
--- a/tests/tests/widget/src/android/widget/cts/GridViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/GridViewTest.java
@@ -63,8 +63,8 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.compatibility.common.util.CtsKeyEventUtil;
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -95,7 +95,7 @@
         mActivity = mActivityRule.getActivity();
         mGridView = (GridView) mActivity.findViewById(R.id.gridview);
 
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     @Test
diff --git a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
index b3392b3..c1fcf20 100644
--- a/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ImageViewTest.java
@@ -324,9 +324,11 @@
         assertNull(mImageViewRegular.getDrawable());
 
         final Drawable drawable = mActivity.getDrawable(R.drawable.testimage);
+        drawable.setLevel(1);
         mImageViewRegular.setImageDrawable(drawable);
         assertTrue(mImageViewRegular.isLayoutRequested());
         assertNotNull(mImageViewRegular.getDrawable());
+        assertEquals(1, mImageViewRegular.getDrawable().getLevel());
         BitmapDrawable testimageBitmap = (BitmapDrawable) drawable;
         Drawable imageViewDrawable = mImageViewRegular.getDrawable();
         BitmapDrawable imageViewBitmap = (BitmapDrawable) imageViewDrawable;
@@ -335,6 +337,21 @@
 
     @UiThreadTest
     @Test
+    public void testSetImageLevelAfterSetImageDrawable() {
+        mImageViewRegular.setImageDrawable(null);
+        assertNull(mImageViewRegular.getDrawable());
+
+        final Drawable drawable = mActivity.getDrawable(R.drawable.testimage);
+        drawable.setLevel(1);
+        mImageViewRegular.setImageDrawable(drawable);
+        assertEquals(1, mImageViewRegular.getDrawable().getLevel());
+        mImageViewRegular.setImageLevel(3);
+        mImageViewRegular.setImageDrawable(drawable);
+        assertEquals(3, mImageViewRegular.getDrawable().getLevel());
+    }
+
+    @UiThreadTest
+    @Test
     public void testSetImageBitmap() {
         mImageViewRegular.setImageBitmap(null);
         // A BitmapDrawable is always created for the ImageView.
diff --git a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
index 8d9e2a2..d26f6c4 100644
--- a/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ListPopupWindowTest.java
@@ -689,6 +689,21 @@
     }
 
     @Test
+    public void testNoDefaultDismissalWithEscapeButton() {
+        mPopupWindowBuilder = new Builder().withDismissListener();
+        WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
+                mPopupWindowBuilder::show);
+
+        // Send ESCAPE key event. As we don't have any custom code that dismisses ListPopupWindow,
+        // and ListPopupWindow doesn't track that system-level key event on its own, ListPopupWindow
+        // should stay visible
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+        verify(mPopupWindowBuilder.mOnDismissListener, never()).onDismiss();
+        assertTrue(mPopupWindow.isShowing());
+    }
+
+
+    @Test
     public void testCustomDismissalWithBackButton() {
         WidgetTestUtils.runOnMainAndDrawSync(mActivityRule, mActivity.getWindow().getDecorView(),
                 () -> {
diff --git a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
index ef3e4cd..fd39af3 100644
--- a/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.java
@@ -52,8 +52,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -92,7 +92,7 @@
     @Before
     public void setup() throws Throwable {
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
 
         mDisplayMetrics = mActivity.getResources().getDisplayMetrics();
         // Do not run the tests, unless the device screen is big enough to fit a magnifier
diff --git a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
index 02cc7e6..bca2bc7 100755
--- a/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/SpinnerTest.java
@@ -425,6 +425,12 @@
         TestUtils.assertAllPixelsOfColor("Drop down should be yellow", dropDownBackground,
                 dropDownBackground.getBounds().width(), dropDownBackground.getBounds().height(),
                 false, Color.YELLOW, 1, true);
+
+        waitForHasFocusMS(SPINNER_HAS_FOCUS_DELAY_MS);
+        // Dismiss the popup with the emulated escape key
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+        // Verify that we're not showing the popup
+        PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing());
     }
 
     @Test
@@ -463,6 +469,11 @@
         PollingCheck.waitFor(() -> mSpinnerDialogMode.isPopupShowing());
         // And test that getPopupBackground returns null
         assertNull(mSpinnerDialogMode.getPopupBackground());
+
+        // Use emulated escape key to close popup
+        mInstrumentation.sendKeyDownUpSync(KeyEvent.KEYCODE_ESCAPE);
+        // Verify that we're not showing the popup
+        PollingCheck.waitFor(() -> !mSpinnerDropdownMode.isPopupShowing());
     }
 
     private void waitForHasFocusMS(int milliseconds) {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java b/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
index e3e1fe1..911e402 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewFadingEdgeTest.java
@@ -37,8 +37,8 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.WidgetTestUtils;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -139,7 +139,7 @@
     @Before
     public void setup() {
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     @Test
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java b/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
index e44acde..9aceba3 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewReceiveContentTest.java
@@ -69,7 +69,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -102,7 +102,7 @@
     @Before
     public void before() {
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mTextView = mActivity.findViewById(R.id.textview_text);
         mDefaultReceiver = new TextViewOnReceiveContentListener();
 
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index c24510a..aafe309 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -66,6 +66,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
 import android.icu.lang.UCharacter;
 import android.net.Uri;
 import android.os.Bundle;
@@ -171,6 +172,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 
 /**
  * Test {@link TextView}.
@@ -8548,6 +8550,14 @@
 
         mTextView.setTextMetricsParams(param);
         assertTrue(param.equals(mTextView.getTextMetricsParams()));
+
+        LineBreakConfig lbConfig = new LineBreakConfig();
+        lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+        mTextView.setLineBreakConfig(lbConfig);
+
+        PrecomputedText.Params resultParams = mTextView.getTextMetricsParams();
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT,
+                resultParams.getLineBreakConfig().getLineBreakStyle());
     }
 
     @Test
@@ -8591,6 +8601,80 @@
     }
 
     @Test
+    public void testLineBreakConfigDefaultValue() {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final TextView textView = new TextView(context);
+        LineBreakConfig lbConfig = textView.getLineBreakConfig();
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE, lbConfig.getLineBreakStyle());
+    }
+
+    @UiThreadTest
+    @Test
+    public void testSetGetLineBreakConfig() {
+        TextView tv = new TextView(mActivity);
+        LineBreakConfig lbConfig = new LineBreakConfig();
+
+        lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+        tv.setLineBreakConfig(lbConfig);
+        LineBreakConfig resultLbConfig = tv.getLineBreakConfig();
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_LOOSE, resultLbConfig.getLineBreakStyle());
+
+        lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NORMAL);
+        tv.setLineBreakConfig(lbConfig);
+        resultLbConfig = tv.getLineBreakConfig();
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NORMAL, resultLbConfig.getLineBreakStyle());
+
+        lbConfig.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+        tv.setLineBreakConfig(lbConfig);
+        resultLbConfig = tv.getLineBreakConfig();
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT, resultLbConfig.getLineBreakStyle());
+    }
+
+    @Test
+    public void testLineBreakConfigByStyle() {
+        TextView defaultTv = findTextView(R.id.textview_line_break_style_default);
+        TextView noneTv = findTextView(R.id.textview_line_break_style_none);
+        TextView looseTv = findTextView(R.id.textview_line_break_style_loose);
+        TextView normalTv = findTextView(R.id.textview_line_break_style_normal);
+        TextView strictTv = findTextView(R.id.textview_line_break_style_strict);
+
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE,
+                defaultTv.getLineBreakConfig().getLineBreakStyle());
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NONE,
+                noneTv.getLineBreakConfig().getLineBreakStyle());
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_LOOSE,
+                looseTv.getLineBreakConfig().getLineBreakStyle());
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_NORMAL,
+                normalTv.getLineBreakConfig().getLineBreakStyle());
+        assertEquals(LineBreakConfig.LINE_BREAK_STYLE_STRICT,
+                strictTv.getLineBreakConfig().getLineBreakStyle());
+    }
+
+    @Test
+    public void testLineBreakConfigEquals() {
+        LineBreakConfig config1 = new LineBreakConfig();
+        config1.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+
+        LineBreakConfig config2 = new LineBreakConfig();
+        config2.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NORMAL);
+
+        LineBreakConfig config3 = new LineBreakConfig();
+        config3.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT);
+
+        assertFalse(config1.equals(config2));
+        assertFalse(config1.equals(config3));
+        assertFalse(config2.equals(config3));
+        assertFalse(Objects.equals(config1, config2));
+        assertFalse(Objects.equals(config1, config3));
+        assertFalse(Objects.equals(config2, config3));
+
+        LineBreakConfig config4 = new LineBreakConfig();
+        config4.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_LOOSE);
+        assertTrue(config1.equals(config4));
+        assertTrue(Objects.equals(config1, config4));
+    }
+
+    @Test
     public void testHyphenationFrequencyDefaultValue() {
         final Context context = InstrumentationRegistry.getTargetContext();
         final TextView textView = new TextView(context);
diff --git a/tests/tests/widget/src/android/widget/cts/TimePickerTest.java b/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
index 9e4e8b3..6f7bbeb 100644
--- a/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TimePickerTest.java
@@ -46,7 +46,7 @@
 
 import com.android.compatibility.common.util.CtsKeyEventUtil;
 import com.android.compatibility.common.util.CtsTouchUtils;
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -78,7 +78,7 @@
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mActivity = mActivityRule.getActivity();
         mTimePicker = (TimePicker) mActivity.findViewById(R.id.timepicker_clock);
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
     }
 
     @Test
diff --git a/tests/tests/widget/src/android/widget/cts/ToastTest.java b/tests/tests/widget/src/android/widget/cts/ToastTest.java
index 01339eb..6857891 100644
--- a/tests/tests/widget/src/android/widget/cts/ToastTest.java
+++ b/tests/tests/widget/src/android/widget/cts/ToastTest.java
@@ -505,7 +505,7 @@
                 lock.notifyAll();
             }
         };
-        manager.addAccessibilityServicesStateChangeListener(listener, null);
+        manager.addAccessibilityServicesStateChangeListener(listener);
         try {
             TestUtils.waitOn(lock,
                     () -> manager.getRecommendedTimeoutMillis(0,
diff --git a/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java b/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
index 7ac3bc5..ff8875e 100644
--- a/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/inline/InlineContentViewTest.java
@@ -32,7 +32,7 @@
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.WindowUtil;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -60,7 +60,7 @@
         mInstrumentation = InstrumentationRegistry.getInstrumentation();
         mContext = mInstrumentation.getTargetContext();
         mActivity = mActivityRule.getActivity();
-        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        WindowUtil.waitForFocus(mActivity);
         mInlineContentView = new InlineContentView(mContext);
     }
 
diff --git a/tests/tests/widget29/OWNERS b/tests/tests/widget29/OWNERS
index 03429e3..d415939 100644
--- a/tests/tests/widget29/OWNERS
+++ b/tests/tests/widget29/OWNERS
@@ -1,6 +1,5 @@
 # Bug component: 25700
 adamp@google.com
 mount@google.com
-shepshapard@google.com
 clarabayarri@google.com
 brufino@google.com
diff --git a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
index 8269843..725415d 100644
--- a/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
+++ b/tests/tests/widget29/src/android/widget/cts29/ToastTest.java
@@ -284,7 +284,7 @@
                 lock.notifyAll();
             }
         };
-        manager.addAccessibilityServicesStateChangeListener(listener, null);
+        manager.addAccessibilityServicesStateChangeListener(listener);
         try {
             TestUtils.waitOn(lock,
                     () -> manager.getRecommendedTimeoutMillis(0,
diff --git a/tests/tests/wifi/AndroidManifest.xml b/tests/tests/wifi/AndroidManifest.xml
index 0e40a37..d2acb5a 100644
--- a/tests/tests/wifi/AndroidManifest.xml
+++ b/tests/tests/wifi/AndroidManifest.xml
@@ -35,6 +35,8 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
+    <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
+                     android:usesPermissionFlags="neverForLocation" />
 
     <!-- usesCleartextTraffic is needed by WifiManagerTest#sendTraffic to send HTTP traffic
          (as opposed to HTTPS). -->
diff --git a/tests/tests/wifi/AndroidTest.xml b/tests/tests/wifi/AndroidTest.xml
index 421a9f7..5da40ea 100644
--- a/tests/tests/wifi/AndroidTest.xml
+++ b/tests/tests/wifi/AndroidTest.xml
@@ -43,6 +43,5 @@
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.wifi" />
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
     </object>
 </configuration>
diff --git a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
index e50d3a6..1e17183 100644
--- a/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/aware/cts/SingleDeviceTest.java
@@ -157,6 +157,14 @@
             mBlocker.countDown();
         }
 
+        @Override
+        public void onShutDown() {
+            synchronized (mLock) {
+                mSessions.remove(mSession);
+            }
+            mSession = null;
+        }
+
         /**
          * Waits for any of the callbacks to be called - or an error (timeout, interruption).
          * Returns one of the ATTACHED, ATTACH_FAILED, or ERROR values.
@@ -164,6 +172,7 @@
         int waitForAnyCallback() {
             try {
                 boolean noTimeout = mBlocker.await(WAIT_FOR_AWARE_CHANGE_SECS, TimeUnit.SECONDS);
+                mBlocker = new CountDownLatch(1);
                 if (noTimeout) {
                     return mCallbackCalled;
                 } else {
@@ -477,6 +486,12 @@
                 characteristics.getMaxServiceSpecificInfoLength(), 255);
         assertEquals("Match Filter Length", characteristics.getMaxMatchFilterLength(), 255);
         assertNotEquals("Cipher suites", characteristics.getSupportedCipherSuites(), 0);
+        assertTrue("Max number of NDP", characteristics.getNumberOfSupportedDataPaths() > 0);
+        assertTrue("Max number of NDI", characteristics.getNumberOfSupportedDataInterfaces() > 0);
+        assertTrue("Max number of Publish sessions",
+                characteristics.getNumberOfSupportedPublishSessions() > 0);
+        assertTrue("Max number of Subscribe sessions",
+                characteristics.getNumberOfSupportedSubscribeSessions() > 0);
         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
             mWifiAwareManager.enableInstantCommunicationMode(true);
             assertEquals(mWifiAwareManager.isInstantCommunicationModeEnabled(),
@@ -548,8 +563,10 @@
             return;
         }
 
-        WifiAwareSession session = attachAndGetSession();
-        session.close();
+        AttachCallbackTest callback = attachAndGetCallback();
+        callback.getSession().close();
+        callback.waitForAnyCallback();
+        assertNull(callback.getSession());
         if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
             assertFalse(mWifiAwareManager.isDeviceAttached());
         }
@@ -1005,4 +1022,14 @@
 
         return session;
     }
+
+    // local utilities
+
+    private AttachCallbackTest attachAndGetCallback() {
+        AttachCallbackTest attachCb = new AttachCallbackTest();
+        mWifiAwareManager.attach(attachCb, mHandler);
+        int cbCalled = attachCb.waitForAnyCallback();
+        assertEquals("Wi-Fi Aware attach", AttachCallbackTest.ATTACHED, cbCalled);
+        return attachCb;
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index 188c2a9..203da55 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -37,16 +37,15 @@
 import android.net.wifi.p2p.WifiP2pManager;
 import android.net.wifi.p2p.nsd.WifiP2pServiceInfo;
 import android.net.wifi.p2p.nsd.WifiP2pUpnpServiceInfo;
-import android.provider.Settings;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
-import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.BitSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -725,11 +724,9 @@
         });
 
         resetResponse(mMyResponse);
-        ShellIdentityUtils.invokeWithShellPermissions(() -> {
-            mWifiP2pManager.stopListening(mWifiP2pChannel, mActionListener);
-            assertTrue(waitForServiceResponse(mMyResponse));
-            assertTrue(mMyResponse.success);
-        });
+        mWifiP2pManager.stopListening(mWifiP2pChannel, mActionListener);
+        assertTrue(waitForServiceResponse(mMyResponse));
+        assertTrue(mMyResponse.success);
     }
 
     public void testP2pService() {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
index 30be41c..2d24d22 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConnectedNetworkScorerTest.java
@@ -23,12 +23,12 @@
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
 import static android.net.wifi.WifiUsabilityStatsEntry.ContentionTimeStats;
-import static android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
-import static android.net.wifi.WifiUsabilityStatsEntry.RateStats;
 import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_FAILURE;
 import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_NO_PROBE;
 import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_SUCCESS;
 import static android.net.wifi.WifiUsabilityStatsEntry.PROBE_STATUS_UNKNOWN;
+import static android.net.wifi.WifiUsabilityStatsEntry.RadioStats;
+import static android.net.wifi.WifiUsabilityStatsEntry.RateStats;
 import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BE;
 import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_BK;
 import static android.net.wifi.WifiUsabilityStatsEntry.WME_ACCESS_CATEGORY_VI;
@@ -41,7 +41,6 @@
 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.junit.Assume.assumeTrue;
 
 import android.annotation.NonNull;
@@ -49,11 +48,11 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiConnectedSessionInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.net.wifi.WifiNetworkSuggestion;
 import android.net.wifi.WifiUsabilityStatsEntry;
-import android.net.wifi.WifiConnectedSessionInfo;
 import android.os.Build;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
@@ -761,7 +760,7 @@
                     }
                     return mTestHelper.testConnectionFlowWithSuggestionWithShellIdentity(
                             testNetwork, suggestionBuilder.build(), executorService,
-                            restrictedNetworkCapabilities);
+                            restrictedNetworkCapabilities, false/* restrictedNetwork */);
                 }
         );
     }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
new file mode 100644
index 0000000..65eef6d
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyMultiInternetWifiNetworkTest.java
@@ -0,0 +1,328 @@
+/*
+ * 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.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.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.TransportInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Build;
+import android.platform.test.annotations.AppModeFull;
+import android.support.test.uiautomator.UiDevice;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.PollingCheck;
+import com.android.compatibility.common.util.ShellIdentityUtils;
+import com.android.modules.utils.build.SdkLevel;
+
+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.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+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#isStaConcurrencyForMultiInternetSupported()}.
+ *
+ * Tests the entire connection flow using issuing connectivity manager requests with
+ * network specifier containing bands.
+ *
+ * Assumes that all the saved networks is either open/WPA1/WPA2/WPA3 authenticated network.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+@AppModeFull(reason = "Cannot get WifiManager in instant app mode")
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class MultiStaConcurrencyMultiInternetWifiNetworkTest extends WifiJUnit4TestBase {
+    private static final String TAG = "MultiStaConcurrencyMultiInternetWifiNetworkTest";
+    private static boolean sWasVerboseLoggingEnabled;
+    private static boolean sWasScanThrottleEnabled;
+    private static boolean sWasWifiEnabled;
+    private static boolean sShouldRunTest = false;
+
+    private Context mContext;
+    private WifiManager mWifiManager;
+    private ConnectivityManager mConnectivityManager;
+    private UiDevice mUiDevice;
+    private ScheduledExecutorService mExecutorService;
+    private TestHelper mTestHelper;
+    // Map from band to list of WifiConfiguration, for matching networks.
+    private Map<Integer, List<WifiConfiguration>> mMatchingNetworksMap;
+    // Map from network SSID to set of bands.
+    private Map<String, Set<Integer>> mMatchingNetworksBands;
+
+    private static final int DURATION_MILLIS = 20_000;
+
+    private final ConnectivityManager.NetworkCallback mNetworkCallback =
+            new ConnectivityManager.NetworkCallback(
+                    ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO) {
+                @Override
+                public void onLinkPropertiesChanged(@NonNull Network network,
+                        @NonNull LinkProperties lp) {
+                    final boolean isPrimary = isPrimaryWifiNetwork(
+                            mConnectivityManager.getNetworkCapabilities(network));
+                    Log.d(TAG, "onLinkPropertiesChanged: " + network + " primary " + isPrimary);
+                }
+
+                @Override
+                public void onCapabilitiesChanged(@NonNull Network network,
+                        @NonNull NetworkCapabilities networkCapabilities) {
+                    final boolean isPrimary = isPrimaryWifiNetwork(
+                            mConnectivityManager.getNetworkCapabilities(network));
+                    Log.d(TAG, "onCapabilitiesChanged: " + network + " primary " + isPrimary
+                            + " cap " + networkCapabilities);
+                }
+
+                @Override
+                public void onLost(@NonNull Network network) {
+                    final boolean isPrimary = isPrimaryWifiNetwork(
+                            mConnectivityManager.getNetworkCapabilities(network));
+                }
+            };
+
+    @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;
+        if (!SdkLevel.isAtLeastT()) return;
+        sShouldRunTest = true;
+        Log.i(TAG, "sShouldRunTest " + sShouldRunTest);
+
+        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
+        ShellIdentityUtils.invokeWithShellPermissions(() -> wifiManager.setWifiEnabled(true));
+        // Make sure wifi is enabled
+        PollingCheck.check("Wifi not enabled", DURATION_MILLIS, () -> wifiManager.isWifiEnabled());
+    }
+
+    @AfterClass
+    public static void tearDownClass() throws Exception {
+        if (!sShouldRunTest) return;
+
+        Context context = InstrumentationRegistry.getInstrumentation().getContext();
+        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 {
+        assumeTrue(sShouldRunTest);
+        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("Wifi not supported", WifiFeature.isWifiSupported(mContext));
+        // Skip if multi STA for internet feature not supported.
+        assumeTrue("isStaConcurrencyForMultiInternetSupported",
+                mWifiManager.isStaConcurrencyForMultiInternetSupported());
+
+        // Turn screen on
+        mTestHelper.turnScreenOn();
+
+        // Clear any existing app state before each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+
+        // This test assumes a CTS test environment with at least 2 connectable bssid's, in
+        // different bands. We need 2 AP's for the test:
+        // 1. Dual-band (DBS) AP [the bands being 2.4 + 5]
+        // 2. Single-band AP with a different SSID.
+        // We need 2 saved networks for the 2 AP's and the device in range to proceed.
+        // The test will check if there are 2 BSSIDs in range and in different bands from
+        // the saved network.
+        List<WifiConfiguration> savedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.getPrivilegedConfiguredNetworks());
+        mMatchingNetworksMap =
+                TestHelper.findMatchingSavedNetworksWithBssidByBand(mWifiManager, savedNetworks);
+        assertWithMessage("Need at least 2 saved network bssids in different bands").that(
+                mMatchingNetworksMap.size()).isAtLeast(2);
+
+        mMatchingNetworksBands = new ArrayMap<>();
+        for (Map.Entry<Integer, List<WifiConfiguration>> entry : mMatchingNetworksMap.entrySet()) {
+            final int band = entry.getKey();
+            for (WifiConfiguration network : entry.getValue()) {
+                if (mMatchingNetworksBands.containsKey(network.SSID)) {
+                    mMatchingNetworksBands.get(network.SSID).add(band);
+                } else {
+                    mMatchingNetworksBands.put(network.SSID, new HashSet<>(Arrays.asList(band)));
+                }
+            }
+        }
+        // Disconnect networks already connected. Make sure the test starts with no network
+        // connections.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    mWifiManager.disconnect();
+                });
+
+        // Wait for Wifi to be disconnected.
+        PollingCheck.check(
+                "Wifi not disconnected",
+                DURATION_MILLIS,
+                () -> mTestHelper.getNumWifiConnections() == 0);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        if (!sShouldRunTest) return;
+        // Re-enable networks.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    for (WifiConfiguration savedNetwork : mWifiManager.getConfiguredNetworks()) {
+                        mWifiManager.enableNetwork(savedNetwork.networkId, false);
+                    }
+                    setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+                });
+
+        mExecutorService.shutdownNow();
+        // Clear any existing app state after each test.
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.removeAppState(myUid(), mContext.getPackageName()));
+        mTestHelper.turnScreenOff();
+    }
+
+    private boolean isPrimaryWifiNetwork(@Nullable NetworkCapabilities networkCapabilities) {
+        if (networkCapabilities == null) {
+            return false;
+        }
+        final TransportInfo transportInfo = networkCapabilities.getTransportInfo();
+        if (!(transportInfo instanceof WifiInfo)) {
+            return false;
+        }
+        return ((WifiInfo) transportInfo).isPrimary();
+    }
+
+    private void setMultiInternetMode(int multiInternetMode) {
+        mWifiManager.setStaConcurrencyForMultiInternetMode(multiInternetMode);
+        try {
+            PollingCheck.check("Wifi not enabled", DURATION_MILLIS,
+                    () -> mWifiManager.isWifiEnabled());
+        } catch (Exception e) {
+            fail("Cant get wifi state");
+        }
+        int mode = mWifiManager.getStaConcurrencyForMultiInternetMode();
+        assertEquals(multiInternetMode, mode);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using enabling multi internet API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkMultiAp()
+            throws Exception {
+        assertWithMessage("Need at least 2 saved network ssids in different bands").that(
+                mMatchingNetworksBands.size()).isAtLeast(2);
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+                });
+        mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, true);
+    }
+
+    /**
+     * Tests the concurrent connection flow.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using enabling multi internet API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkDBS() throws Exception {
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DBS_AP);
+                });
+
+        mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, true);
+    }
+
+    /**
+     * Tests the concurrent connection flow fails without enabling the MultiInternetState.
+     * 1. Connect to a network using internet connectivity API.
+     * 2. Connect to a network using enabling multi internet API.
+     * 3. Verify that both connections are active.
+     */
+    @Test
+    public void testConnectToSecondaryNetworkWhenConnectedToInternetNetworkFail() throws Exception {
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> {
+                    setMultiInternetMode(WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+                });
+
+        mTestHelper.testMultiInternetConnectionFlowWithShellIdentity(mExecutorService, false);
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
index 271035f..8b030da 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyRestrictedWifiNetworkSuggestionTest.java
@@ -244,7 +244,7 @@
                         .build();
         mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
                 mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
-                Set.of(NET_CAPABILITY_OEM_PAID));
+                Set.of(NET_CAPABILITY_OEM_PAID), false/* restrictedNetwork */);
 
         // Ensure that there are 2 wifi connections available for apps.
         assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
@@ -266,7 +266,7 @@
                         .build();
         mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
                 mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
-                Set.of(NET_CAPABILITY_OEM_PAID));
+                Set.of(NET_CAPABILITY_OEM_PAID), false);
 
         // Now trigger internet connectivity.
         mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
@@ -296,7 +296,7 @@
                         .build();
         mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
                 mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
-                Set.of(NET_CAPABILITY_OEM_PRIVATE));
+                Set.of(NET_CAPABILITY_OEM_PRIVATE), false/* restrictedNetwork */);
 
         // Ensure that there are 2 wifi connections available for apps.
         assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
@@ -318,7 +318,7 @@
                         .build();
         mNsNetworkCallback = mTestHelper.testConnectionFlowWithSuggestion(
                 mTestNetworkForRestrictedConnection, suggestion, mExecutorService,
-                Set.of(NET_CAPABILITY_OEM_PRIVATE));
+                Set.of(NET_CAPABILITY_OEM_PRIVATE), false/* restrictedNetwork */);
 
         // Now trigger internet connectivity.
         mNetworkCallback = mTestHelper.testConnectionFlowWithConnect(
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
index 98ba803..06ed9a2 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ScanResultTest.java
@@ -19,9 +19,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
 
-import java.nio.ByteBuffer;
-import java.util.List;
-
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,13 +28,19 @@
 import android.net.wifi.WifiInfo;
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.WifiLock;
+import android.net.wifi.WifiSsid;
 import android.platform.test.annotations.AppModeFull;
-import android.test.AndroidTestCase;
+import android.support.test.uiautomator.UiDevice;
+
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.ShellIdentityUtils;
 import com.android.compatibility.common.util.SystemUtil;
 
+import java.nio.ByteBuffer;
+import java.util.List;
+
 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
 public class ScanResultTest extends WifiJUnit3TestBase {
     private static class MySync {
@@ -46,6 +49,7 @@
 
     private WifiManager mWifiManager;
     private WifiLock mWifiLock;
+    private TestHelper mTestHelper;
     private static MySync mMySync;
     private boolean mWasVerboseLoggingEnabled;
     private boolean mWasScanThrottleEnabled;
@@ -67,6 +71,9 @@
     private static final long SCAN_FIND_BSSID_WAIT_MSEC = 5_000L;
     private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
 
+    // Note: defined in ScanRequestProxy.java
+    public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4;
+
     private static final String TEST_SSID = "TEST_SSID";
     public static final String TEST_BSSID = "04:ac:fe:45:34:10";
     public static final String TEST_CAPS = "CCMP";
@@ -119,6 +126,9 @@
         mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE);
         assertThat(mWifiManager).isNotNull();
 
+        mTestHelper = new TestHelper(InstrumentationRegistry.getInstrumentation().getContext(),
+                UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()));
+
         // turn on verbose logging for tests
         mWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
                 () -> mWifiManager.isVerboseLoggingEnabled());
@@ -283,6 +293,7 @@
 
         ScanResult scanResult = new ScanResult();
         scanResult.SSID = TEST_SSID;
+        scanResult.setWifiSsid(WifiSsid.fromUtf8Text(TEST_SSID));
         scanResult.BSSID = TEST_BSSID;
         scanResult.capabilities = TEST_CAPS;
         scanResult.level = TEST_LEVEL;
@@ -291,6 +302,7 @@
 
         ScanResult scanResult2 = new ScanResult(scanResult);
         assertThat(scanResult2.SSID).isEqualTo(TEST_SSID);
+        assertThat(scanResult2.getWifiSsid()).isEqualTo(scanResult.getWifiSsid());
         assertThat(scanResult2.BSSID).isEqualTo(TEST_BSSID);
         assertThat(scanResult2.capabilities).isEqualTo(TEST_CAPS);
         assertThat(scanResult2.level).isEqualTo(TEST_LEVEL);
@@ -338,5 +350,33 @@
                 .that("\"" + scanResultSsidUnquoted + "\"")
                 .isEqualTo(wifiInfoSsidQuoted);
         assertThat(currentNetwork.frequency).isEqualTo(wifiInfo.getFrequency());
+        assertThat(currentNetwork.getSecurityTypes())
+                .asList().contains(wifiInfo.getCurrentSecurityType());
+    }
+
+    /**
+     * Verify that scan throttling is enforced.
+     */
+    public void testScanThrottling() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // re-enable scan throttling
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.setScanThrottleEnabled(true));
+        mTestHelper.turnScreenOn();
+
+        synchronized (mMySync) {
+            for (int i = 0; i < SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS; ++i) {
+                mWifiManager.startScan();
+                assertTrue("Iteration #" + i,
+                        waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_RESULTS_AVAILABLE));
+            }
+
+            mWifiManager.startScan();
+            assertTrue("Should be throttled", waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_FAILURE));
+        }
     }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index 98ab11d..02193c3 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -20,6 +20,7 @@
 import static android.Manifest.permission.NETWORK_SETTINGS;
 import static android.net.ConnectivityManager.NetworkCallback.FLAG_INCLUDE_LOCATION_INFO;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
 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;
@@ -27,6 +28,11 @@
 
 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;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
@@ -47,16 +53,19 @@
 import android.os.WorkSource;
 import android.support.test.uiautomator.UiDevice;
 import android.text.TextUtils;
+import android.util.ArrayMap;
 import android.util.Log;
 
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ApiLevelUtil;
+import com.android.compatibility.common.util.PollingCheck;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
@@ -104,8 +113,12 @@
         private final CountDownLatch mCountDownLatch;
         public boolean onAvailableCalled = false;
 
-        TestScanResultsCallback(CountDownLatch countDownLatch) {
-            mCountDownLatch = countDownLatch;
+        TestScanResultsCallback() {
+            mCountDownLatch = new CountDownLatch(1);
+        }
+
+        public void await() throws InterruptedException {
+            mCountDownLatch.await(DURATION_MILLIS, TimeUnit.MILLISECONDS);
         }
 
         @Override
@@ -125,28 +138,51 @@
      *
      * @param wifiManager WifiManager service
      * @param savedNetworks List of saved networks on the device.
+     * @return List of WifiConfiguration with matching bssid.
      */
     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);
+        Map<Integer, List<WifiConfiguration>> networksMap =
+                findMatchingSavedNetworksWithBssidByBand(wifiManager, savedNetworks);
+        for (List<WifiConfiguration> configs : networksMap.values()) {
+            matchingNetworksWithBssids.addAll(configs);
+        }
+        return matchingNetworksWithBssids;
+    }
+
+    /**
+     * Loops through all the saved networks available in the scan results. Returns a map of lists 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.
+     * @return Map from band to the list of WifiConfiguration with matching bssid.
+     */
+    public static Map<Integer, List<WifiConfiguration>> findMatchingSavedNetworksWithBssidByBand(
+            @NonNull WifiManager wifiManager, @NonNull List<WifiConfiguration> savedNetworks) {
+        if (savedNetworks.isEmpty()) return Collections.emptyMap();
+        Map<Integer, List<WifiConfiguration>> matchingNetworksWithBssids = new ArrayMap<>();
         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);
+            TestScanResultsCallback scanResultsCallback = new TestScanResultsCallback();
             try {
                 wifiManager.registerScanResultsCallback(
                         Executors.newSingleThreadExecutor(), scanResultsCallback);
                 wifiManager.startScan(new WorkSource(myUid()));
                 // now wait for callback
-                countDownLatch.await(DURATION_MILLIS, TimeUnit.MILLISECONDS);
+                scanResultsCallback.await();
             } catch (InterruptedException e) {
             } finally {
                 wifiManager.unregisterScanResultsCallback(scanResultsCallback);
             }
             List<ScanResult> scanResults = wifiManager.getScanResults();
-            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            if (scanResults == null || scanResults.isEmpty()) continue;
             for (ScanResult scanResult : scanResults) {
                 WifiConfiguration matchingNetwork = savedNetworks.stream()
                         .filter(network -> TextUtils.equals(
@@ -157,7 +193,13 @@
                     // 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);
+                    List<WifiConfiguration> bandConfigs = matchingNetworksWithBssids.get(
+                            scanResult.getBand());
+                    if (bandConfigs == null) {
+                        bandConfigs = new ArrayList<>();
+                        matchingNetworksWithBssids.put(scanResult.getBand(), bandConfigs);
+                    }
+                    bandConfigs.add(matchingNetworkCopy);
                 }
             }
             if (!matchingNetworksWithBssids.isEmpty()) break;
@@ -226,47 +268,70 @@
                 .setBssid(MacAddress.fromString(network.BSSID));
     }
 
-    private static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
-        private final CountDownLatch mCountDownLatch;
+    public static class TestNetworkCallback extends ConnectivityManager.NetworkCallback {
+        private CountDownLatch mBlocker;
         public boolean onAvailableCalled = false;
         public boolean onUnavailableCalled = false;
+        public boolean onLostCalled = false;
         public NetworkCapabilities networkCapabilities;
 
-        TestNetworkCallback(@NonNull CountDownLatch countDownLatch) {
-            mCountDownLatch = countDownLatch;
+        TestNetworkCallback() {
+            mBlocker = new CountDownLatch(1);
         }
 
-        TestNetworkCallback(@NonNull CountDownLatch countDownLatch, int flags) {
+        TestNetworkCallback(int flags) {
             super(flags);
-            mCountDownLatch = countDownLatch;
+            mBlocker = new CountDownLatch(1);
+        }
+
+        public boolean await(long timeout) throws Exception {
+            return mBlocker.await(timeout, TimeUnit.MILLISECONDS);
         }
 
         @Override
         public void onAvailable(Network network) {
+            Log.i(TAG, "onAvailable " + network);
             onAvailableCalled = true;
         }
 
         @Override
         public void onCapabilitiesChanged(Network network,
                 NetworkCapabilities networkCapabilities) {
+            Log.i(TAG, "onCapabilitiesChanged " + network);
             this.networkCapabilities = networkCapabilities;
-            mCountDownLatch.countDown();
+            mBlocker.countDown();
         }
 
         @Override
         public void onUnavailable() {
+            Log.i(TAG, "onUnavailable ");
             onUnavailableCalled = true;
-            mCountDownLatch.countDown();
+            mBlocker.countDown();
+        }
+
+        @Override
+        public void onLost(Network network) {
+            onLostCalled = true;
+            mBlocker.countDown();
+        }
+
+        boolean waitForAnyCallback(int timeout) {
+            try {
+                boolean noTimeout = mBlocker.await(timeout, TimeUnit.MILLISECONDS);
+                mBlocker = new CountDownLatch(1);
+                return noTimeout;
+            } catch (InterruptedException e) {
+                return false;
+            }
         }
     }
 
-    private static TestNetworkCallback createTestNetworkCallback(
-            @NonNull CountDownLatch countDownLatch) {
+    private static TestNetworkCallback createTestNetworkCallback() {
         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
             // flags for NetworkCallback only introduced in S.
-            return new TestNetworkCallback(countDownLatch, FLAG_INCLUDE_LOCATION_INFO);
+            return new TestNetworkCallback(FLAG_INCLUDE_LOCATION_INFO);
         } else {
-            return new TestNetworkCallback(countDownLatch);
+            return new TestNetworkCallback();
         }
     }
 
@@ -320,9 +385,8 @@
     public ConnectivityManager.NetworkCallback testConnectionFlowWithConnect(
             @NonNull WifiConfiguration network) throws Exception {
         CountDownLatch countDownLatchAl = new CountDownLatch(1);
-        CountDownLatch countDownLatchNr = new CountDownLatch(1);
         TestActionListener actionListener = new TestActionListener(countDownLatchAl);
-        TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatchNr);
+        TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
             uiAutomation.adoptShellPermissionIdentity();
@@ -346,8 +410,8 @@
             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.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+                    .isTrue();
             assertThat(testNetworkCallback.onAvailableCalled).isTrue();
             final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
             assertConnectionEquals(network, wifiInfo);
@@ -379,15 +443,18 @@
      * @param restrictedNetworkCapabilities Whether this connection should be restricted with
      *                                    the provided capability.
      *
+     * @param isRestricted whether the suggestion is for a restricted network
      * @return NetworkCallback used for the connection (can be used by client to release the
      * connection.
      */
     public ConnectivityManager.NetworkCallback testConnectionFlowWithSuggestionWithShellIdentity(
             WifiConfiguration network, WifiNetworkSuggestion suggestion,
             @NonNull ScheduledExecutorService executorService,
-            @NonNull Set<Integer> restrictedNetworkCapabilities) throws Exception {
+            @NonNull Set<Integer> restrictedNetworkCapabilities,
+            boolean isRestricted) throws Exception {
         return testConnectionFlowWithSuggestionInternal(
-                network, suggestion, executorService, restrictedNetworkCapabilities, true);
+                network, suggestion, executorService, restrictedNetworkCapabilities, true,
+                isRestricted);
     }
 
     /**
@@ -402,19 +469,22 @@
      * @param restrictedNetworkCapabilities Whether this connection should be restricted with
      *                                    the provided capability.
      *
+     * @param isRestricted whether the suggestion is for a restricted network
      * @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,
-            @NonNull Set<Integer> restrictedNetworkCapabilities) throws Exception {
+            @NonNull Set<Integer> restrictedNetworkCapabilities,
+            boolean isRestricted) throws Exception {
         final UiAutomation uiAutomation =
                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
             uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
             return testConnectionFlowWithSuggestionWithShellIdentity(
-                    network, suggestion, executorService, restrictedNetworkCapabilities);
+                    network, suggestion, executorService, restrictedNetworkCapabilities,
+                    isRestricted);
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
@@ -441,7 +511,8 @@
         try {
             uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
             return testConnectionFlowWithSuggestionInternal(
-                    network, suggestion, executorService, restrictedNetworkCapabilities, false);
+                    network, suggestion, executorService, restrictedNetworkCapabilities, false,
+                    false/* restrictedNetwork */);
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
@@ -457,6 +528,7 @@
      *                                    the provided capability.
      * @param expectConnectionSuccess Whether to expect connection success or not.
      *
+     * @param isRestricted whether the suggestion is for a restricted network
      * @return NetworkCallback used for the connection (can be used by client to release the
      * connection.
      */
@@ -464,16 +536,15 @@
             WifiConfiguration network, WifiNetworkSuggestion suggestion,
             @NonNull ScheduledExecutorService executorService,
             @NonNull Set<Integer> restrictedNetworkCapabilities,
-            boolean expectConnectionSuccess) throws Exception {
-        CountDownLatch countDownLatch = new CountDownLatch(1);
+            boolean expectConnectionSuccess, boolean isRestricted) throws Exception {
         // File the network request & wait for the callback.
-        TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatch);
+        TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
         try {
             // File a request for restricted (oem paid) wifi network.
             NetworkRequest.Builder nrBuilder = new NetworkRequest.Builder()
                     .addTransportType(TRANSPORT_WIFI)
                     .addCapability(NET_CAPABILITY_INTERNET);
-            if (restrictedNetworkCapabilities.isEmpty()) {
+            if (restrictedNetworkCapabilities.isEmpty() && !isRestricted) {
                 // If not a restricted connection, a network callback is sufficient.
                 mConnectivityManager.registerNetworkCallback(
                         nrBuilder.build(), testNetworkCallback);
@@ -481,6 +552,7 @@
                 for (Integer restrictedNetworkCapability : restrictedNetworkCapabilities) {
                     nrBuilder.addCapability(restrictedNetworkCapability);
                 }
+                nrBuilder.removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
                 mConnectivityManager.requestNetwork(nrBuilder.build(), testNetworkCallback);
             }
             // Add wifi network suggestion.
@@ -496,22 +568,21 @@
             }, 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
+                        .waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS)).isTrue();
                 assertThat(testNetworkCallback.onAvailableCalled).isTrue();
                 final WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
                 assertConnectionEquals(network, wifiInfo);
-                if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(mContext)) {
-                    assertThat(wifiInfo.isTrusted()).isTrue();
-                    WifiInfo redact = wifiInfo
-                            .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
-                    assertThat(wifiInfo.getInformationElements()).isNotNull();
-                    assertThat(redact.getInformationElements()).isNull();
-                    assertThat(redact.getApplicableRedactions()).isEqualTo(
-                            NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
-                            | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
-                            | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS);
-                }
+                assertThat(wifiInfo.isTrusted()).isTrue();
+                assertThat(wifiInfo.isRestricted()).isEqualTo(isRestricted);
+                WifiInfo redact = wifiInfo
+                        .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
+                assertThat(wifiInfo.getInformationElements()).isNotNull();
+                assertThat(redact.getInformationElements()).isNull();
+                assertThat(redact.getApplicableRedactions()).isEqualTo(
+                        NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
+                                | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
+                                | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS);
                 if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
                     // If STA concurrency for restricted connection is supported, this should not
                     // be the primary connection.
@@ -524,8 +595,8 @@
                 }
             } else {
                 // now wait for connection to timeout.
-                assertThat(countDownLatch.await(
-                        DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isFalse();
+                assertThat(testNetworkCallback
+                        .waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS)).isFalse();
             }
         } catch (Throwable e /* catch assertions & exceptions */) {
             try {
@@ -672,9 +743,8 @@
     public ConnectivityManager.NetworkCallback testConnectionFlowWithSpecifierWithShellIdentity(
             WifiConfiguration network, WifiNetworkSpecifier specifier, boolean shouldUserReject)
             throws Exception {
-        CountDownLatch countDownLatch = new CountDownLatch(1);
         // File the network request & wait for the callback.
-        TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatch);
+        TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
 
         // Fork a thread to handle the UI interactions.
         Thread uiThread = new Thread(() -> {
@@ -703,8 +773,8 @@
             // Start the UI interactions.
             uiThread.run();
             // now wait for callback
-            assertThat(countDownLatch.await(
-                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(testNetworkCallback.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+                    .isTrue();
             if (shouldUserReject) {
                 assertThat(testNetworkCallback.onUnavailableCalled).isTrue();
             } else {
@@ -771,8 +841,9 @@
     public long getNumWifiConnections() {
         Network[] networks = mConnectivityManager.getAllNetworks();
         return Arrays.stream(networks)
-                .filter(n ->
-                        mConnectivityManager.getNetworkCapabilities(n).hasTransport(TRANSPORT_WIFI))
+                .filter(n -> mConnectivityManager.getNetworkCapabilities(n) != null
+                        && mConnectivityManager.getNetworkCapabilities(n)
+                                .hasTransport(TRANSPORT_WIFI))
                 .count();
     }
 
@@ -783,8 +854,7 @@
      * @throws Exception
      */
     public void assertWifiInternetConnectionAvailable() throws Exception {
-        CountDownLatch countDownLatchNr = new CountDownLatch(1);
-        TestNetworkCallback testNetworkCallback = createTestNetworkCallback(countDownLatchNr);
+        TestNetworkCallback testNetworkCallback = createTestNetworkCallback();
         try {
             // File a callback for wifi network.
             NetworkRequest.Builder builder = new NetworkRequest.Builder()
@@ -799,8 +869,8 @@
             mConnectivityManager.registerNetworkCallback(builder.build(), testNetworkCallback);
             // Wait for connection to complete & ensure we are connected to some network capable
             // of providing internet access.
-            assertThat(countDownLatchNr.await(
-                    DURATION_NETWORK_CONNECTION_MILLIS, TimeUnit.MILLISECONDS)).isTrue();
+            assertThat(testNetworkCallback.waitForAnyCallback(DURATION_NETWORK_CONNECTION_MILLIS))
+                    .isTrue();
             assertThat(testNetworkCallback.onAvailableCalled).isTrue();
         } finally {
             mConnectivityManager.unregisterNetworkCallback(testNetworkCallback);
@@ -825,4 +895,168 @@
         }
     }
 
+    /**
+     * Create a network request for specified band in a network specifier.
+     */
+    private NetworkRequest createNetworkRequestForInternet(int band) {
+        final NetworkRequest networkRequest = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(TRANSPORT_WIFI)
+                .setNetworkSpecifier(new WifiNetworkSpecifier.Builder()
+                        .setBand(band).build())
+                .build();
+        return networkRequest;
+    }
+
+    /**
+     * Check if a wifi network info is as expected for multi internet connections.
+     * @return the WifiInfo of the network.
+     */
+    private WifiInfo checkWifiNetworkInfo(TestNetworkCallback testNetworkCallback,
+            int band) {
+        if (testNetworkCallback.networkCapabilities == null) {
+            return null;
+        }
+        WifiInfo wifiInfo = getWifiInfo(testNetworkCallback.networkCapabilities);
+        assertTrue(wifiInfo.isTrusted());
+        assertFalse(wifiInfo.isRestricted());
+        WifiInfo redact = wifiInfo
+                .makeCopy(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION);
+        assertNotNull(wifiInfo.getInformationElements());
+        assertNull(redact.getInformationElements());
+        assertEquals(NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION
+                        | NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS
+                        | NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS,
+                        redact.getApplicableRedactions());
+        assertEquals(band, getBandFromFrequency(wifiInfo.getFrequency()));
+
+        return wifiInfo;
+    }
+
+    /**
+     * Tests the entire connection success/failure flow using the provided suggestion.
+     *
+     * @param executorService Excutor service to run scan periodically (to trigger connection).
+     * @param expectConnectionSuccess Whether to expect connection success or not.
+     */
+    private void testMultiInternetConnectionFlowInternal(
+            @NonNull ScheduledExecutorService executorService,
+            boolean expectConnectionSuccess) throws Exception {
+        // File the network request & wait for the callback.
+        TestNetworkCallback testNetworkCallback2G = createTestNetworkCallback();
+        TestNetworkCallback testNetworkCallback5G = createTestNetworkCallback();
+        final NetworkRequest networkRequest2G = createNetworkRequestForInternet(
+                ScanResult.WIFI_BAND_24_GHZ);
+        final NetworkRequest networkRequest5G = createNetworkRequestForInternet(
+                ScanResult.WIFI_BAND_5_GHZ);
+         // Make sure wifi is connected to primary after wifi enabled with saved network.
+        PollingCheck.check("Wifi not connected", DURATION_NETWORK_CONNECTION_MILLIS,
+                () -> getNumWifiConnections() > 0);
+        try {
+            // Request both 2G and 5G wifi networks.
+            mConnectivityManager.requestNetwork(networkRequest2G, testNetworkCallback2G);
+            mConnectivityManager.requestNetwork(networkRequest5G, testNetworkCallback5G);
+            // Wait for the request to reach the wifi stack before kick-start periodic scans.
+            Thread.sleep(200);
+            boolean band2gFound = false;
+            boolean band5gFound = false;
+            // now wait for connection to complete and wait for callback
+            WifiInfo primaryInfo = null;
+            WifiInfo secondaryInfo = null;
+            if (testNetworkCallback2G.await(DURATION_NETWORK_CONNECTION_MILLIS)) {
+                WifiInfo info2g = checkWifiNetworkInfo(testNetworkCallback2G,
+                        ScanResult.WIFI_BAND_24_GHZ);
+                if (info2g != null) {
+                    if (info2g.isPrimary()) {
+                        primaryInfo = info2g;
+                    } else {
+                        secondaryInfo = info2g;
+                    }
+                    band2gFound = true;
+                }
+            }
+            if (testNetworkCallback5G.await(DURATION_NETWORK_CONNECTION_MILLIS)) {
+                WifiInfo info5g = checkWifiNetworkInfo(testNetworkCallback5G,
+                        ScanResult.WIFI_BAND_5_GHZ);
+                if (info5g != null) {
+                    if (info5g.isPrimary()) {
+                        primaryInfo = info5g;
+                    } else {
+                        secondaryInfo = info5g;
+                    }
+                    band5gFound = true;
+                }
+            }
+            if (expectConnectionSuccess) {
+                // Ensure both primary and non-primary networks are created.
+                assertTrue("Network not found on 2g", band2gFound);
+                assertTrue("Network not found on 5g", band5gFound);
+                assertFalse("Network unavailable on 2g", testNetworkCallback2G.onUnavailableCalled);
+                assertFalse("Network unavailable on 5g", testNetworkCallback5G.onUnavailableCalled);
+                assertNotNull("No primary network info", primaryInfo);
+                assertNotNull("No secondary network info", secondaryInfo);
+                assertFalse("Primary and secondary networks are same",
+                        primaryInfo.equals(secondaryInfo));
+                // Ensure that there are 2 wifi connections available for apps.
+                assertEquals("Expecting 2 Wifi networks", 2, getNumWifiConnections());
+                // Check if the networks meets the expected requested multi internet state
+                int mode = mWifiManager.getStaConcurrencyForMultiInternetMode();
+                if (mode == mWifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP) {
+                    // Multi AP state allows connecting to same network or multi APs in other
+                    // networks, with different BSSIDs.
+                    assertFalse("Can not connect to same bssid" + primaryInfo.getBSSID()
+                            + " / " + secondaryInfo.getBSSID(),
+                            TextUtils.equals(primaryInfo.getBSSID(), secondaryInfo.getBSSID()));
+                } else if (mode == mWifiManager.WIFI_MULTI_INTERNET_MODE_DBS_AP) {
+                    assertTrue("NETWORK_DBS mode can only connect to the same SSID but got "
+                            + primaryInfo.getSSID() + " / " + secondaryInfo.getSSID(),
+                            TextUtils.equals(primaryInfo.getSSID(), secondaryInfo.getSSID()));
+                    assertEquals("NETWORK_DBS mode can only connect to the same network Id but got"
+                            + primaryInfo.getNetworkId() + " / " + secondaryInfo.getNetworkId(),
+                            primaryInfo.getNetworkId(), secondaryInfo.getNetworkId());
+                    assertEquals("NETWORK_DBS mode can only connect to same security type but got"
+                            + primaryInfo.getCurrentSecurityType() + " / "
+                            + secondaryInfo.getCurrentSecurityType(),
+                            primaryInfo.getCurrentSecurityType(),
+                            secondaryInfo.getCurrentSecurityType());
+                } else {
+                    fail("Invalid multi internet mode " + mode);
+                }
+            } else {
+                // Ensure no band specified wifi connection is created.
+                assertTrue(testNetworkCallback2G.onUnavailableCalled
+                        || testNetworkCallback5G.onUnavailableCalled);
+                // Only one wifi network
+                assertEquals("There should be only one wifi network but got "
+                        + getNumWifiConnections(), 1, getNumWifiConnections());
+            }
+        } finally {
+            mConnectivityManager.unregisterNetworkCallback(testNetworkCallback2G);
+            mConnectivityManager.unregisterNetworkCallback(testNetworkCallback5G);
+            executorService.shutdown();
+        }
+    }
+
+    /**
+     * Tests the entire connection success flow using the provided suggestion.
+     *
+     * Note: The caller needs to invoke this after acquiring shell identity.
+     *
+     * @param executorService Excutor service to run scan periodically (to trigger connection).
+     * @param expectConnectionSuccess Whether to expect connection success or not.
+     */
+    public void testMultiInternetConnectionFlowWithShellIdentity(
+            @NonNull ScheduledExecutorService executorService,
+            boolean expectConnectionSuccess) throws Exception {
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity(NETWORK_SETTINGS, CONNECTIVITY_INTERNAL);
+            testMultiInternetConnectionFlowInternal(
+                    executorService, expectConnectionSuccess);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
index 3bd8b28..8f76791 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiConfigurationTest.java
@@ -16,6 +16,10 @@
 
 package android.net.wifi.cts;
 
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_AUTO;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NONE;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_NON_PERSISTENT;
+import static android.net.wifi.WifiConfiguration.RANDOMIZATION_PERSISTENT;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
 import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
@@ -140,4 +144,20 @@
         configuration.setDeletionPriority(1);
         assertEquals(1, configuration.getDeletionPriority());
     }
-}
+
+    public void testSetGetMacRandomizationSetting() throws Exception {
+        WifiConfiguration configuration = new WifiConfiguration();
+
+        configuration.setMacRandomizationSetting(RANDOMIZATION_NONE);
+        assertEquals(RANDOMIZATION_NONE, configuration.getMacRandomizationSetting());
+
+        configuration.setMacRandomizationSetting(RANDOMIZATION_PERSISTENT);
+        assertEquals(RANDOMIZATION_PERSISTENT, configuration.getMacRandomizationSetting());
+
+        configuration.setMacRandomizationSetting(RANDOMIZATION_NON_PERSISTENT);
+        assertEquals(RANDOMIZATION_NON_PERSISTENT, configuration.getMacRandomizationSetting());
+
+        configuration.setMacRandomizationSetting(RANDOMIZATION_AUTO);
+        assertEquals(RANDOMIZATION_AUTO, configuration.getMacRandomizationSetting());
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index a9402ba..16cc8a0 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -1009,4 +1009,37 @@
         config.setDecoratedIdentityPrefix(TEST_DECORATED_IDENTITY_PREFIX);
         assertEquals(TEST_DECORATED_IDENTITY_PREFIX, config.getDecoratedIdentityPrefix());
     }
+
+    // TODO(b/196180536): Wait for T SDK finalization before changing
+    // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+    @SdkSuppress(minSdkVersion = 31)
+    public void testSetGetTrustOnFirstUse() {
+        if (!hasWifi()) {
+            return;
+        }
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        assertFalse(config.isTrustOnFirstUseEnabled());
+        config.enableTrustOnFirstUse(true);
+        assertTrue(config.isTrustOnFirstUseEnabled());
+        config.enableTrustOnFirstUse(false);
+        assertFalse(config.isTrustOnFirstUseEnabled());
+    }
+
+    // TODO(b/196180536): Wait for T SDK finalization before changing
+    // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+    @SdkSuppress(minSdkVersion = 31)
+    public void testHasCaCertificate() {
+        if (!hasWifi()) {
+            return;
+        }
+        WifiEnterpriseConfig config = new WifiEnterpriseConfig();
+        assertFalse(config.hasCaCertificate());
+        config.setCaPath("/tmp/ca.cert");
+        assertTrue(config.hasCaCertificate());
+
+        config = new WifiEnterpriseConfig();
+        assertFalse(config.hasCaCertificate());
+        config.setCaCertificate(FakeKeys.CA_CERT0);
+        assertTrue(config.hasCaCertificate());
+    }
 }
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
index d884a50..baa266b 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit3TestBase.java
@@ -49,8 +49,9 @@
     @Override
     protected void tearDown() throws Exception {
         if (mWasLocationEnabledForTest) {
-            mLocationManager.setLocationEnabledForUser(
-                    false, UserHandle.getUserHandleForUid(Process.myUid()));
+            ShellIdentityUtils.invokeWithShellPermissions(() ->
+                    mLocationManager.setLocationEnabledForUser(
+                            false, UserHandle.getUserHandleForUid(Process.myUid())));
         }
 
         super.tearDown();
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
index 6bc6885..13fa031 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiJUnit4TestBase.java
@@ -55,8 +55,9 @@
     @After
     public void disableLocationIfOriginallyDisabled() throws Exception {
         if (mWasLocationEnabledForTest) {
-            mLocationManager.setLocationEnabledForUser(
-                    false, UserHandle.getUserHandleForUid(Process.myUid()));
+            ShellIdentityUtils.invokeWithShellPermissions(() ->
+                    mLocationManager.setLocationEnabledForUser(
+                            false, UserHandle.getUserHandleForUid(Process.myUid())));
         }
     }
 }
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 a8dfee4..668dcd1 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -63,6 +63,8 @@
 import android.net.wifi.WifiManager.WifiLock;
 import android.net.wifi.WifiNetworkConnectionStatistics;
 import android.net.wifi.WifiNetworkSuggestion;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.WifiSsid;
 import android.net.wifi.hotspot2.ConfigParser;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
@@ -510,7 +512,7 @@
             fail("Please enable location for this test - since Marshmallow WiFi scan results are"
                     + " empty when location is disabled!");
         }
-        runWithScanningEnabled(() -> {
+        runWithScanning(() -> {
             setWifiEnabled(false);
             Thread.sleep(TEST_WAIT_DURATION_MS);
             startScan();
@@ -525,7 +527,7 @@
             final String TAG = "Test";
             assertNotNull(mWifiManager.createWifiLock(TAG));
             assertNotNull(mWifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL, TAG));
-        });
+        }, true /* run with enabled*/);
     }
 
     /**
@@ -988,18 +990,18 @@
         }
 
         TestExecutor executor = new TestExecutor();
-        TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+        TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         List<Integer> supportedSoftApBands = new ArrayList<>();
         try {
             uiAutomation.adoptShellPermissionIdentity();
-            verifyRegisterSoftApCallback(executor, capabilityCallback);
+            verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
             supportedSoftApBands = getSupportedSoftApBand(
-                    capabilityCallback.getCurrentSoftApCapability());
+                    lohsSoftApCallback.getCurrentSoftApCapability());
         } catch (Exception ex) {
         } finally {
             // clean up
-            mWifiManager.unregisterSoftApCallback(capabilityCallback);
+            mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
             uiAutomation.dropShellPermissionIdentity();
         }
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
@@ -1148,6 +1150,38 @@
     }
 
     /**
+     * Verify setting the scan schedule.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testSetScreenOnScanSchedule() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.setScreenOnScanSchedule(new int[] {20, 40}, new int[] {
+                        WifiScanner.SCAN_TYPE_HIGH_ACCURACY, WifiScanner.SCAN_TYPE_LOW_LATENCY}));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.setScreenOnScanSchedule(null, null));
+    }
+
+    /**
+     * Verify a normal app cannot set the scan schedule.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testSetScreenOnScanScheduleNoPermission() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        try {
+            mWifiManager.setScreenOnScanSchedule(null, null);
+            fail("A normal app should not be able to call this API.");
+        } catch (SecurityException e) {
+        }
+    }
+
+    /**
      * Test coverage for the constructor of AddNetworkResult.
      */
     public void testAddNetworkResultCreation() {
@@ -1168,6 +1202,39 @@
     }
 
     /**
+     * Verify {@link WifiManager#setSsidsDoNotBlocklist(Set)} can be called with sufficient
+     * privilege.
+     */
+    public void testGetAndSetSsidsDoNotBlocklist() {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        Set<WifiSsid> ssids = new ArraySet<>();
+        ssids.add(WifiSsid.fromString("\"TEST_SSID_1\""));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.setSsidsDoNotBlocklist(ssids));
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> assertEquals("Ssids should match", ssids,
+                        mWifiManager.getSsidsDoNotBlocklist()));
+
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> mWifiManager.setSsidsDoNotBlocklist(Collections.EMPTY_SET));
+        ShellIdentityUtils.invokeWithShellPermissions(
+                () -> assertEquals("Should equal to empty set",
+                        Collections.EMPTY_SET,
+                        mWifiManager.getSsidsDoNotBlocklist()));
+
+        try {
+            mWifiManager.setSsidsDoNotBlocklist(Collections.EMPTY_SET);
+            fail("Expected SecurityException when called without permission");
+        } catch (SecurityException e) {
+            // expect the exception
+        }
+    }
+
+    /**
      * Verify that {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} throws a
      * SecurityException when called by a normal app.
      */
@@ -1214,6 +1281,36 @@
     }
 
     /**
+     * Verify {@link WifiManager#getPrivilegedConnectedNetwork()} returns the currently
+     * connected WifiConfiguration with randomized MAC address filtered out.
+     */
+    public void testGetPrivilegedConnectedNetworkSuccess() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        setWifiEnabled(true);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            mWifiManager.startScan();
+            waitForConnection(); // ensures that there is at-least 1 saved network on the device.
+
+            WifiInfo wifiInfo = mWifiManager.getConnectionInfo();
+            int curNetworkId = wifiInfo.getNetworkId();
+            assertNotEquals("Should be connected to valid networkId", INVALID_NETWORK_ID,
+                    curNetworkId);
+            WifiConfiguration curConfig = mWifiManager.getPrivilegedConnectedNetwork();
+            assertEquals("NetworkId should match", curNetworkId, curConfig.networkId);
+            assertEquals("SSID should match", wifiInfo.getSSID(), curConfig.SSID);
+            assertEquals("Randomized MAC should be filtered out", WifiInfo.DEFAULT_MAC_ADDRESS,
+                    curConfig.getRandomizedMacAddress().toString());
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * Verify {@link WifiManager#addNetworkPrivileged(WifiConfiguration)} works properly when the
      * calling app has permissions.
      */
@@ -1332,14 +1429,15 @@
             // Skip the test if wifi module version is older than S.
             return;
         }
-        List<WifiConfiguration> testConfigs = new ArrayList<>();
-        testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
-        testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
-        testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
-        testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
-        testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+        List<WifiConfiguration> baseConfigs = new ArrayList<>();
+        baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
+        baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
+        baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
                 WifiConfiguration.SECURITY_TYPE_EAP));
-        testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+        List<WifiConfiguration> upgradeConfigs = new ArrayList<>();
+        upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
+        upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
+        upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
                 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
@@ -1349,46 +1447,51 @@
                     mWifiManager.getPrivilegedConfiguredNetworks().size();
             final int originalCallerConfiguredNetworksNumber =
                 mWifiManager.getCallerConfiguredNetworks().size();
-            for (WifiConfiguration c: testConfigs) {
+            for (WifiConfiguration c: baseConfigs) {
                 WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
                 assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
                 assertTrue(result.networkId >= 0);
                 c.networkId = result.networkId;
             }
-            List<WifiConfiguration> expectedConfigs = testConfigs;
+            for (WifiConfiguration c: upgradeConfigs) {
+                WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
+                assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+                assertTrue(result.networkId >= 0);
+                c.networkId = result.networkId;
+            }
+            // open/owe, psk/sae, and wpa2e/wpa3e should be merged
+            // so they should have the same network ID.
+            for (int i = 0; i < baseConfigs.size(); i++) {
+                assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId);
+            }
+
+            int numAddedConfigs = baseConfigs.size();
+            List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs);
             if (SdkLevel.isAtLeastS()) {
-                // open/owe, psk/sae, and wpa2e/wpa3e should be merged
-                // so they should have the same network ID.
-                assertEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
-                assertEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
-                assertEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
-            } else {
-                // Network IDs for different security types should be unique for R
-                assertNotEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
-                assertNotEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
-                assertNotEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
-                // WPA3-Enterprise is omitted when WPA2-Enterprise is present for R
-                expectedConfigs = testConfigs.subList(0, 5);
+                // S devices and above will return one additional config per each security type
+                // added, so we include the number of both base and upgrade configs.
+                numAddedConfigs += upgradeConfigs.size();
+                expectedConfigs.addAll(upgradeConfigs);
             }
             List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
-            assertEquals(originalConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalConfiguredNetworksNumber + numAddedConfigs,
                     configuredNetworks.size());
             assertConfigsAreFound(expectedConfigs, configuredNetworks);
 
             List<WifiConfiguration> privilegedConfiguredNetworks =
                     mWifiManager.getPrivilegedConfiguredNetworks();
-            assertEquals(originalPrivilegedConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs,
                     privilegedConfiguredNetworks.size());
             assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks);
 
             List<WifiConfiguration> callerConfiguredNetworks =
                     mWifiManager.getCallerConfiguredNetworks();
-            assertEquals(originalCallerConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs,
                     callerConfiguredNetworks.size());
             assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks);
 
         } finally {
-            for (WifiConfiguration c: testConfigs) {
+            for (WifiConfiguration c: baseConfigs) {
                 if (c.networkId >= 0) {
                     mWifiManager.removeNetwork(c.networkId);
                 }
@@ -1463,6 +1566,73 @@
         }
     }
 
+    private SoftApConfiguration.Builder generateSoftApConfigBuilderWithSsid(String ssid) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            return new SoftApConfiguration.Builder().setWifiSsid(WifiSsid.fromUtf8Text(ssid));
+        }
+        return new SoftApConfiguration.Builder().setSsid(ssid);
+    }
+
+    private void assertSsidEquals(SoftApConfiguration config, String expectedSsid) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            assertEquals(expectedSsid, config.getWifiSsid().getUtf8Text());
+        } else {
+            assertEquals(expectedSsid, config.getSsid());
+        }
+    }
+
+    public void testStartLocalOnlyHotspotWithSupportedBand() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // check that softap mode is supported by the device
+        if (!mWifiManager.isPortableHotspotSupported()) {
+            return;
+        }
+
+        TestExecutor executor = new TestExecutor();
+        TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        boolean wifiEnabled = mWifiManager.isWifiEnabled();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
+            SoftApConfiguration.Builder customConfigBuilder =
+                    generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
+                    .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+
+            SparseIntArray testBandsAndChannels = getAvailableBandAndChannelForTesting(
+                    lohsSoftApCallback.getCurrentSoftApCapability());
+
+            for (int i = 0; i < testBandsAndChannels.size(); i++) {
+                TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
+                int testBand = testBandsAndChannels.keyAt(i);
+                customConfigBuilder.setBand(testBand);
+                mWifiManager.startLocalOnlyHotspot(customConfigBuilder.build(), executor, callback);
+                // now wait for callback
+                Thread.sleep(TEST_WAIT_DURATION_MS);
+
+                // Verify callback is run on the supplied executor
+                assertFalse(callback.onStartedCalled);
+                executor.runAll();
+                assertTrue(callback.onStartedCalled);
+                assertNotNull(callback.reservation);
+                SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
+                assertEquals(TEST_SSID_UNQUOTED, softApConfig.getWifiSsid().getUtf8Text());
+                assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
+                assertEquals(testBand, softApConfig.getBand());
+                assertTrue(lohsSoftApCallback.getCurrentSoftApInfo().getFrequency() > 0);
+                stopLocalOnlyHotspot(callback, wifiEnabled);
+            }
+        } finally {
+            // clean up
+            mWifiManager.unregisterSoftApCallback(lohsSoftApCallback);
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     public void testStartLocalOnlyHotspotWithConfigBssid() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -1475,17 +1645,17 @@
 
         TestExecutor executor = new TestExecutor();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
-        TestSoftApCallback capabilityCallback = new TestSoftApCallback(mLock);
+        TestSoftApCallback lohsSoftApCallback = new TestSoftApCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         boolean wifiEnabled = mWifiManager.isWifiEnabled();
         try {
             uiAutomation.adoptShellPermissionIdentity();
-            verifyRegisterSoftApCallback(executor, capabilityCallback);
-            SoftApConfiguration.Builder customConfigBuilder = new SoftApConfiguration.Builder()
-                    .setSsid(TEST_SSID_UNQUOTED)
+            verifyLohsRegisterSoftApCallback(executor, lohsSoftApCallback);
+            SoftApConfiguration.Builder customConfigBuilder =
+                    generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
 
-            boolean isSupportCustomizedMac = capabilityCallback.getCurrentSoftApCapability()
+            boolean isSupportCustomizedMac = lohsSoftApCallback.getCurrentSoftApCapability()
                         .areFeaturesSupported(
                         SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
                     && PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
@@ -1509,12 +1679,12 @@
             if (isSupportCustomizedMac) {
                 assertEquals(TEST_MAC, softApConfig.getBssid());
             }
-            assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
+            assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
             assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
         } finally {
             // clean up
             stopLocalOnlyHotspot(callback, wifiEnabled);
-            mWifiManager.unregisterSoftApCallback(capabilityCallback);
+            mWifiManager.unregisterLocalOnlyHotspotSoftApCallback(lohsSoftApCallback);
             uiAutomation.dropShellPermissionIdentity();
         }
     }
@@ -1528,10 +1698,11 @@
         if (!mWifiManager.isPortableHotspotSupported()) {
             return;
         }
-        SoftApConfiguration customConfig = new SoftApConfiguration.Builder()
-                .setSsid(TEST_SSID_UNQUOTED)
+        SoftApConfiguration customConfig =
+                generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                 .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                 .build();
+
         TestExecutor executor = new TestExecutor();
         TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(mLock);
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1551,7 +1722,7 @@
             assertNotNull(callback.reservation);
             SoftApConfiguration softApConfig = callback.reservation.getSoftApConfiguration();
             assertNotNull(softApConfig);
-            assertEquals(TEST_SSID_UNQUOTED, softApConfig.getSsid());
+            assertSsidEquals(softApConfig, TEST_SSID_UNQUOTED);
             assertEquals(TEST_PASSPHRASE, softApConfig.getPassphrase());
         } finally {
             // clean up
@@ -1924,19 +2095,19 @@
         }
     }
 
-    private void runWithScanningEnabled(ThrowingRunnable r) throws Exception {
-        boolean wasScanEnabledForTest = false;
-        if (!mWifiManager.isScanAlwaysAvailable()) {
+    private void runWithScanning(ThrowingRunnable r, boolean isEnabled) throws Exception {
+        boolean scanModeChangedForTest = false;
+        if (mWifiManager.isScanAlwaysAvailable() != isEnabled) {
             ShellIdentityUtils.invokeWithShellPermissions(
-                    () -> mWifiManager.setScanAlwaysAvailable(true));
-            wasScanEnabledForTest = true;
+                    () -> mWifiManager.setScanAlwaysAvailable(isEnabled));
+            scanModeChangedForTest = true;
         }
         try {
             r.run();
         } finally {
-            if (wasScanEnabledForTest) {
+            if (scanModeChangedForTest) {
                 ShellIdentityUtils.invokeWithShellPermissions(
-                        () -> mWifiManager.setScanAlwaysAvailable(false));
+                        () -> mWifiManager.setScanAlwaysAvailable(!isEnabled));
             }
         }
     }
@@ -1964,7 +2135,7 @@
             fail("Please enable location for this test - since Marshmallow WiFi scan results are"
                     + " empty when location is disabled!");
         }
-        runWithScanningEnabled(() -> {
+        runWithScanning(() -> {
             setWifiEnabled(false);
             turnScreenOn();
             assertWifiScanningIsOn();
@@ -1973,7 +2144,7 @@
             assertWifiScanningIsOn();
             turnScreenOn();
             assertWifiScanningIsOn();
-        });
+        }, true /* run with enabled*/);
     }
 
     /**
@@ -1998,7 +2169,7 @@
             fail("Please enable location for this test - since Marshmallow WiFi scan results are"
                     + " empty when location is disabled!");
         }
-        runWithScanningEnabled(() -> {
+        runWithScanning(() -> {
             setWifiEnabled(true);
             turnScreenOn();
             assertWifiScanningIsOn();
@@ -2007,7 +2178,7 @@
             assertWifiScanningIsOn();
             turnScreenOn();
             assertWifiScanningIsOn();
-        });
+        }, true /* run with enabled*/);
     }
 
     /**
@@ -2032,6 +2203,22 @@
                 () -> {
                     executor.runAll();
                     // Verify callback is run on the supplied executor and called
+                    return callback.getOnStateChangedCalled()
+                            && callback.getOnSoftapInfoChangedCalledCount() > 0
+                            && callback.getOnSoftApCapabilityChangedCalled()
+                            && callback.getOnConnectedClientCalled();
+                });
+    }
+
+    private void verifyLohsRegisterSoftApCallback(TestExecutor executor,
+            TestSoftApCallback callback) throws Exception {
+        // Register callback to get SoftApCapability
+        mWifiManager.registerLocalOnlyHotspotSoftApCallback(executor, callback);
+        PollingCheck.check(
+                "SoftAp register failed!", 5_000,
+                () -> {
+                    executor.runAll();
+                    // Verify callback is run on the supplied executor and called
                     return callback.getOnStateChangedCalled() &&
                             callback.getOnSoftapInfoChangedCalledCount() > 0 &&
                             callback.getOnSoftApCapabilityChangedCalled() &&
@@ -2047,10 +2234,23 @@
         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.S)) {
             assertTrue(currentConfig.isUserConfiguration());
         }
+
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            // Verify set/get with the deprecated set/getSsid()
+            SoftApConfiguration oldSsidConfig = new SoftApConfiguration.Builder(targetConfig)
+                    .setWifiSsid(null)
+                    .setSsid(targetConfig.getSsid()).build();
+            mWifiManager.setSoftApConfiguration(oldSsidConfig);
+            currentConfig = mWifiManager.getSoftApConfiguration();
+            compareSoftApConfiguration(oldSsidConfig, currentConfig);
+        }
     }
 
     private void compareSoftApConfiguration(SoftApConfiguration currentConfig,
         SoftApConfiguration testSoftApConfig) {
+        if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+            assertEquals(currentConfig.getWifiSsid(), testSoftApConfig.getWifiSsid());
+        }
         assertEquals(currentConfig.getSsid(), testSoftApConfig.getSsid());
         assertEquals(currentConfig.getBssid(), testSoftApConfig.getBssid());
         assertEquals(currentConfig.getSecurityType(), testSoftApConfig.getSecurityType());
@@ -2079,6 +2279,10 @@
                     testSoftApConfig.isBridgedModeOpportunisticShutdownEnabled());
             assertEquals(currentConfig.isIeee80211axEnabled(),
                     testSoftApConfig.isIeee80211axEnabled());
+            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+                assertEquals(currentConfig.getBridgedModeOpportunisticShutdownTimeoutMillis(),
+                        testSoftApConfig.getBridgedModeOpportunisticShutdownTimeoutMillis());
+            }
         }
     }
 
@@ -2096,7 +2300,6 @@
             PollingCheck.check(
                 "SoftAp turn off failed!", 2_000,
                 () -> mWifiManager.isWifiApEnabled() == false);
-            mTetheringManager.stopTethering(ConnectivityManager.TETHERING_WIFI);
         }
     }
 
@@ -2175,11 +2378,12 @@
             int[] expectedBands = {SoftApConfiguration.BAND_2GHZ,
                     SoftApConfiguration.BAND_2GHZ | SoftApConfiguration.BAND_5GHZ};
             // Test bridged SoftApConfiguration set and get (setBands)
-            SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
-                    .setSsid(TEST_SSID_UNQUOTED)
+            SoftApConfiguration testSoftApConfig =
+                    generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                     .setBands(expectedBands)
                     .build();
+
             boolean shouldFallbackToSingleAp = shouldFallbackToSingleAp(testBands,
                     callback.getCurrentSoftApCapability());
             verifySetGetSoftApConfig(testSoftApConfig);
@@ -2242,8 +2446,8 @@
                 dual_channels.put(SoftApConfiguration.BAND_5GHZ,
                         callback.getCurrentSoftApCapability()
                         .getSupportedChannelList(SoftApConfiguration.BAND_5GHZ)[0]);
-                SoftApConfiguration testSoftApConfig = new SoftApConfiguration.Builder()
-                        .setSsid(TEST_SSID_UNQUOTED)
+                SoftApConfiguration testSoftApConfig =
+                        generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                         .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                         .setChannels(dual_channels)
                         .build();
@@ -2294,8 +2498,8 @@
             turnOffWifiAndTetheredHotspotIfEnabled();
             verifyRegisterSoftApCallback(executor, callback);
 
-            SoftApConfiguration.Builder softApConfigBuilder = new SoftApConfiguration.Builder()
-                    .setSsid(TEST_SSID_UNQUOTED)
+            SoftApConfiguration.Builder softApConfigBuilder =
+                     generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                     .setAutoShutdownEnabled(true)
                     .setShutdownTimeoutMillis(100000)
@@ -2303,6 +2507,10 @@
                             callback.getCurrentSoftApCapability()).keyAt(0))
                     .setHiddenSsid(false);
 
+            if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)) {
+                softApConfigBuilder.setBridgedModeOpportunisticShutdownTimeoutMillis(30_000);
+            }
+
             // Test SoftApConfiguration set and get
             verifySetGetSoftApConfig(softApConfigBuilder.build());
 
@@ -2400,8 +2608,8 @@
                     SoftApCapability.SOFTAP_FEATURE_MAC_ADDRESS_CUSTOMIZATION)
                     && PropertyUtil.isVndkApiLevelNewerThan(Build.VERSION_CODES.S);
 
-            SoftApConfiguration.Builder testSoftApConfigBuilder = new SoftApConfiguration.Builder()
-                    .setSsid(TEST_SSID_UNQUOTED)
+            SoftApConfiguration.Builder testSoftApConfigBuilder =
+                     generateSoftApConfigBuilderWithSsid(TEST_SSID_UNQUOTED)
                     .setPassphrase(TEST_PASSPHRASE, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
                     .setChannel(testBandsAndChannels.valueAt(0), testBandsAndChannels.keyAt(0));
 
@@ -2542,17 +2750,8 @@
             assertTrue(actionListener.onSuccessCalled);
             // Wait for connection to complete & ensure we are connected to the saved network.
             waitForConnection();
-            if (SdkLevel.isAtLeastS()) {
-                assertEquals(savedNetworkToConnect.networkId,
-                        mWifiManager.getConnectionInfo().getNetworkId());
-            } else {
-                // In R, auto-upgraded network IDs may be different from the original saved network.
-                // Since we may end up selecting the auto-upgraded network ID for connection and end
-                // up connected to the original saved network with a different network ID, we should
-                // instead match by SSID.
-                assertEquals(savedNetworkToConnect.SSID,
-                        mWifiManager.getConnectionInfo().getSSID());
-            }
+            assertEquals(savedNetworkToConnect.networkId,
+                    mWifiManager.getConnectionInfo().getNetworkId());
         } finally {
             // Re-enable all saved networks before exiting.
             if (savedNetworks != null) {
@@ -3302,6 +3501,110 @@
         waitForConnection();
     }
 
+    private class TestActiveCountryCodeChangedCallback implements
+            WifiManager.ActiveCountryCodeChangedCallback  {
+        private String mCurrentCountryCode;
+        private boolean mIsOnActiveCountryCodeChangedCalled = false;
+        private boolean mIsOnCountryCodeInactiveCalled = false;
+
+        public boolean isOnActiveCountryCodeChangedCalled() {
+            return mIsOnActiveCountryCodeChangedCalled;
+        }
+
+        public boolean isOnCountryCodeInactiveCalled() {
+            return mIsOnCountryCodeInactiveCalled;
+        }
+        public void resetCallbackCallededHistory() {
+            mIsOnActiveCountryCodeChangedCalled = false;
+            mIsOnCountryCodeInactiveCalled = false;
+        }
+
+        public String getCurrentDriverCountryCode() {
+            return mCurrentCountryCode;
+        }
+
+        @Override
+        public void onActiveCountryCodeChanged(String country) {
+            Log.d(TAG, "Receive DriverCountryCodeChanged to " + country);
+            mCurrentCountryCode = country;
+            mIsOnActiveCountryCodeChangedCalled = true;
+        }
+
+        @Override
+        public void onCountryCodeInactive() {
+            mCurrentCountryCode = null;
+            mIsOnCountryCodeInactiveCalled = true;
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
+    public void testActiveCountryCodeChangedCallback() throws Exception {
+        TestActiveCountryCodeChangedCallback testCountryCodeChangedCallback =
+                new TestActiveCountryCodeChangedCallback();
+        TestExecutor executor = new TestExecutor();
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            turnOffWifiAndTetheredHotspotIfEnabled();
+            // Run with scanning disable to make sure there is no active mode.
+            runWithScanning(() -> {
+                mWifiManager.registerActiveCountryCodeChangedCallback(
+                        executor, testCountryCodeChangedCallback);
+
+
+                PollingCheck.check(
+                        "DriverCountryCode is non-null when wifi off",
+                        5000,
+                        () -> {
+                            executor.runAll();
+                            return testCountryCodeChangedCallback
+                                        .isOnActiveCountryCodeChangedCalled()
+                                    && testCountryCodeChangedCallback.getCurrentDriverCountryCode()
+                                            == null;
+                        });
+                // Enable wifi to make sure country code has been updated.
+                setWifiEnabled(true);
+                PollingCheck.check(
+                        "DriverCountryCode is null when wifi on",
+                        5000,
+                        () -> {
+                            executor.runAll();
+                            return testCountryCodeChangedCallback
+                                        .isOnActiveCountryCodeChangedCalled()
+                                    && testCountryCodeChangedCallback.getCurrentDriverCountryCode()
+                                            != null;
+                        });
+                // Disable wifi to trigger country code change
+                setWifiEnabled(false);
+                PollingCheck.check(
+                        "DriverCountryCode should be null when wifi off",
+                        5000,
+                        () -> {
+                            executor.runAll();
+                            return testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
+                                    && testCountryCodeChangedCallback
+                                            .getCurrentDriverCountryCode() == null;
+                        });
+                mWifiManager.unregisterActiveCountryCodeChangedCallback(
+                            testCountryCodeChangedCallback);
+                testCountryCodeChangedCallback.resetCallbackCallededHistory();
+                setWifiEnabled(true);
+                // Check there is no callback has been called.
+                PollingCheck.check(
+                        "Callback is called after unregister",
+                        5000,
+                        () -> {
+                            executor.runAll();
+                            return !testCountryCodeChangedCallback.isOnCountryCodeInactiveCalled()
+                                    && !testCountryCodeChangedCallback
+                                            .isOnActiveCountryCodeChangedCalled();
+                        });
+            }, false /* Run with disabled */);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Test that the wifi country code is either null, or a length-2 string.
      */
@@ -3775,7 +4078,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
         verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
-                WifiConfiguration.RANDOMIZATION_NON_PERSISTENT);
+                WifiNetworkSuggestion.RANDOMIZATION_NON_PERSISTENT);
 
         suggestion = new WifiNetworkSuggestion.Builder()
                 .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
@@ -3783,7 +4086,7 @@
         assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
                 mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
         verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
-                WifiConfiguration.RANDOMIZATION_PERSISTENT);
+                WifiNetworkSuggestion.RANDOMIZATION_PERSISTENT);
     }
 
     private void verifySuggestionFoundWithMacRandomizationSetting(String ssid,
@@ -3791,8 +4094,7 @@
         List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
         for (WifiNetworkSuggestion entry : retrievedSuggestions) {
             if (entry.getSsid().equals(ssid)) {
-                assertEquals(macRandomizationSetting,
-                        entry.getWifiConfiguration().macRandomizationSetting);
+                assertEquals(macRandomizationSetting, entry.getMacRandomizationSetting());
                 return; // pass test after the MAC randomization setting is verified.
             }
         }
@@ -3994,6 +4296,10 @@
             mWifiManager.startScan();
             ensureNotConnected();
 
+            // verify null is returned when attempting to get current configured network.
+            WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
+            assertNull("config should be null because wifi is not connected", config);
+
             // Now enable autojoin on all networks.
             mWifiManager.allowAutojoinGlobal(true);
 
@@ -4148,6 +4454,20 @@
         }
     }
 
+    /**
+     * Tests {@link WifiManager#isTrustOnFirstUseSupported()} does not crash.
+     */
+    // TODO(b/196180536): Wait for T SDK finalization before changing
+    // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)`
+    @SdkSuppress(minSdkVersion = 31)
+    public void testIsTrustOnFirstUseSupported() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        mWifiManager.isTrustOnFirstUseSupported();
+    }
+
     public class TestCoexCallback extends WifiManager.CoexCallback {
         private Object mCoexLock;
         private int mOnCoexUnsafeChannelChangedCount;
@@ -4391,6 +4711,60 @@
     }
 
     /**
+     * Tests {@link WifiManager#setWifiPasspointEnabled)} raise security exception without
+     * permission.
+     */
+    // TODO(b/139192273): Wait for T SDK finalization before changing
+    // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
+    @SdkSuppress(minSdkVersion = 31)
+    public void testEnablePasspointWithoutPermission() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        try {
+            mWifiManager.setWifiPasspointEnabled(true);
+            fail("setWifiPasspointEnabled() expected to fail - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests {@link WifiManager#setWifiPasspointEnabled)} does not crash and returns success.
+     */
+    // TODO(b/139192273): Wait for T SDK finalization before changing
+    // to `@SdkSuppress(minSdkVersion = Build.VERSION_CODES.T)`
+    @SdkSuppress(minSdkVersion = 31)
+    public void testEnablePasspoint() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+
+        // The below API only works with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Check if passpoint is enabled by default.
+            assertTrue(mWifiManager.isWifiPasspointEnabled());
+            // Try to disable passpoint
+            mWifiManager.setWifiPasspointEnabled(false);
+            PollingCheck.check(
+                "Wifi passpoint turn off failed!", 2_000,
+                () -> mWifiManager.isWifiPasspointEnabled() == false);
+            // Try to enable passpoint
+            mWifiManager.setWifiPasspointEnabled(true);
+            PollingCheck.check(
+                "Wifi passpoint turn on failed!", 2_000,
+                () -> mWifiManager.isWifiPasspointEnabled() == true);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    /**
      * Tests {@link WifiManager#isDecoratedIdentitySupported)} does not crash.
      */
     public void testIsDecoratedIdentitySupported() throws Exception {
@@ -4570,4 +4944,73 @@
             mBlocker.countDown();
         }
     }
+
+    /**
+     * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
+     * without permission.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testIsStaConcurrencyForMultiInternetSupported() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        // ensure no crash.
+        mWifiManager.isStaConcurrencyForMultiInternetSupported();
+    }
+
+    /**
+     * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} raise security exception
+     * without permission.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testSetStaConcurrencyForMultiInternetModeWithoutPermission() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())
+                || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
+            // skip the test if WiFi is not supported or multi internet feature not supported.
+            return;
+        }
+        try {
+            mWifiManager.setStaConcurrencyForMultiInternetMode(
+                    WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+            fail("setWifiPasspointEnabled() expected to fail - privileged call");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Tests {@link WifiManager#setStaConcurrencyForMultiInternetMode)} does not crash.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    public void testSetStaConcurrencyForMultiInternetMode() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())
+                || !mWifiManager.isStaConcurrencyForMultiInternetSupported()) {
+            // skip the test if WiFi is not supported or multi internet feature not supported.
+            return;
+        }
+
+        // The below API only works with privileged permissions (obtained via shell identity
+        // for test)
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        try {
+            uiAutomation.adoptShellPermissionIdentity();
+            // Try to disable multi internet
+            mWifiManager.setStaConcurrencyForMultiInternetMode(
+                    WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+            PollingCheck.check(
+                    "Wifi multi internet disable failed!", 2_000,
+                    () -> mWifiManager.getStaConcurrencyForMultiInternetMode()
+                            == WifiManager.WIFI_MULTI_INTERNET_MODE_DISABLED);
+            // Try to enable multi internet
+            mWifiManager.setStaConcurrencyForMultiInternetMode(
+                    WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+            PollingCheck.check(
+                    "Wifi multi internet turn on failed!", 2_000,
+                    () -> mWifiManager.getStaConcurrencyForMultiInternetMode()
+                            == WifiManager.WIFI_MULTI_INTERNET_MODE_MULTI_AP);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
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 5e54e9b..7190952 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiNetworkSuggestionTest.java
@@ -23,7 +23,6 @@
 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;
@@ -46,6 +45,7 @@
 import android.net.wifi.hotspot2.pps.Credential;
 import android.net.wifi.hotspot2.pps.HomeSp;
 import android.os.Build;
+import android.os.ParcelUuid;
 import android.platform.test.annotations.AppModeFull;
 import android.support.test.uiautomator.UiDevice;
 import android.telephony.TelephonyManager;
@@ -93,6 +93,11 @@
     private static final int TEST_PRIORITY = 5;
     private static final int TEST_PRIORITY_GROUP = 1;
     private static final int TEST_SUB_ID = 1;
+    private static final ParcelUuid GROUP_UUID = ParcelUuid
+            .fromString("0000110B-0000-1000-8000-00805F9B34FB");
+    private static final int DURATION_NETWORK_DISCONNECT_MILLIS = 3_000;
+    private static final int DURATION_NETWORK_LINGER_MILLIS = 30_000;
+    private static final int DURATION_NETWORK_UPDATE = 10_000;
 
     private static boolean sWasVerboseLoggingEnabled;
     private static boolean sWasScanThrottleEnabled;
@@ -993,6 +998,20 @@
     }
 
     /**
+     * Tests {@link android.net.wifi.WifiNetworkSuggestion.Builder} class, with SubscriptionGroup
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
+    @Test
+    public void testBuilderWithSubscriptionGroup() throws Exception {
+        WifiNetworkSuggestion suggestion =
+                new WifiNetworkSuggestion.Builder()
+                        .setSsid(TEST_SSID)
+                        .setSubscriptionGroup(GROUP_UUID)
+                        .build();
+        assertEquals(GROUP_UUID, suggestion.getSubscriptionGroup());
+    }
+
+    /**
      * Helper function for creating a {@link PasspointConfiguration} for testing.
      *
      * @return {@link PasspointConfiguration}
@@ -1101,11 +1120,12 @@
     }
 
     /**
-     * Connect to a network using suggestion API.
+     * Connect to a network using suggestion API then remove with
+     * {@link WifiManager#ACTION_REMOVE_SUGGESTION_DISCONNECT}
      */
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     @Test
-    public void testConnectToSuggestion() throws Exception {
+    public void testConnectToSuggestionThenRemoveWithImmediateDisconnect() throws Exception {
         assertNotNull(sTestNetwork);
         WifiNetworkSuggestion suggestion =
                 TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
@@ -1113,7 +1133,60 @@
                         .build();
         sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
                 sTestNetwork, suggestion, mExecutorService,
-                Set.of() /* restrictedNetworkCapability */);
+                Set.of()/* restrictedNetworkCapability */, false/* restrictedNetwork */);
+        TestHelper.TestNetworkCallback callback = (TestHelper.TestNetworkCallback)
+                sNsNetworkCallback;
+        while (callback.waitForAnyCallback(DURATION_NETWORK_UPDATE));
+        sWifiManager.removeNetworkSuggestions(List.of(suggestion),
+                WifiManager.ACTION_REMOVE_SUGGESTION_DISCONNECT);
+        callback.waitForAnyCallback(DURATION_NETWORK_DISCONNECT_MILLIS);
+        assertTrue(callback.onLostCalled);
+    }
+
+    /**
+     * Connect to a network using suggestion API, then remove with
+     * {@link WifiManager#ACTION_REMOVE_SUGGESTION_LINGER}
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testConnectToSuggestionThenRemoveWithLingering() throws Exception {
+        assertNotNull(sTestNetwork);
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                                sTestNetwork)
+                        .build();
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService,
+                Set.of()/* restrictedNetworkCapability */, false/* restrictedNetwork */);
+        TestHelper.TestNetworkCallback callback = (TestHelper.TestNetworkCallback)
+                sNsNetworkCallback;
+        while (callback.waitForAnyCallback(DURATION_NETWORK_UPDATE));
+        sWifiManager.removeNetworkSuggestions(List.of(suggestion),
+                WifiManager.ACTION_REMOVE_SUGGESTION_LINGER);
+        callback.waitForAnyCallback(DURATION_NETWORK_DISCONNECT_MILLIS);
+        // Should not disconnect immediately
+        assertFalse(callback.onLostCalled);
+        // After linger time out, should disconnect.
+        Thread.sleep(DURATION_NETWORK_LINGER_MILLIS);
+        assertTrue(callback.onLostCalled);
+    }
+
+    /**
+     * Connect to a restricted network using suggestion API.
+     */
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    @Test
+    public void testConnectToRestrictedSuggestion() throws Exception {
+        assertNotNull(sTestNetwork);
+        WifiNetworkSuggestion suggestion =
+                TestHelper.createSuggestionBuilderWithCredentialFromSavedNetworkWithBssid(
+                        sTestNetwork)
+                        .setRestricted(true)
+                        .build();
+        assertTrue(suggestion.isRestricted());
+        sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
+                sTestNetwork, suggestion, mExecutorService,
+                Set.of()/* restrictedNetworkCapability */, true);
     }
 
     /**
@@ -1129,7 +1202,7 @@
                         .setOemPaid(true)
                         .build();
         sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
-                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID));
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PAID), false);
     }
 
     /**
@@ -1147,7 +1220,7 @@
                         .build();
         sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
                 sTestNetwork, suggestion, mExecutorService,
-                Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE));
+                Set.of(NET_CAPABILITY_OEM_PAID, NET_CAPABILITY_OEM_PRIVATE), false);
     }
 
     /**
@@ -1163,7 +1236,8 @@
                         .setOemPrivate(true)
                         .build();
         sNsNetworkCallback = sTestHelper.testConnectionFlowWithSuggestion(
-                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE));
+                sTestNetwork, suggestion, mExecutorService, Set.of(NET_CAPABILITY_OEM_PRIVATE),
+                false);
     }
 
     /**
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java
new file mode 100644
index 0000000..acf8d28
--- /dev/null
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiSsidTest.java
@@ -0,0 +1,209 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.net.wifi.WifiSsid;
+import android.os.Parcel;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+
+public class WifiSsidTest extends WifiJUnit3TestBase {
+
+    private static final String TEST_SSID_UTF_8 = "Test SSID";
+    private static final String TEST_SSID_UTF_8_QUOTED = "\"" + TEST_SSID_UTF_8 + "\"";
+    private static final byte[] TEST_SSID_UTF_8_BYTES =
+            TEST_SSID_UTF_8.getBytes(StandardCharsets.UTF_8);
+    private static final String TEST_SSID_UTF_8_HEX = "546573742053534944";
+
+    private static final byte[] TEST_SSID_NON_UTF_8_BYTES =
+            "服務集識別碼".getBytes(Charset.forName("GBK"));
+    private static final String TEST_SSID_NON_UTF_8_HEX = "B7FE84D5BCAFD7528465B461";
+
+    /**
+     * Verify the behavior of fromByteArray()
+     */
+    public void testFromByteArray() {
+        WifiSsid wifiSsidUtf8 = WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES);
+        assertThat(wifiSsidUtf8).isNotNull();
+        assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+        assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+        assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+        WifiSsid wifiSsidNonUtf8 = WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES);
+        assertThat(wifiSsidNonUtf8).isNotNull();
+        assertThat(wifiSsidNonUtf8.getBytes()).isEqualTo(TEST_SSID_NON_UTF_8_BYTES);
+        assertThat(wifiSsidNonUtf8.getUtf8Text()).isNull();
+        assertThat(wifiSsidNonUtf8.toString()).isEqualTo(TEST_SSID_NON_UTF_8_HEX);
+
+        WifiSsid wifiSsidEmpty = WifiSsid.fromBytes(new byte[0]);
+        assertThat(wifiSsidEmpty).isNotNull();
+        assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+        assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+        WifiSsid wifiSsidNull = WifiSsid.fromBytes(null);
+        assertThat(wifiSsidNull).isNotNull();
+        assertThat(wifiSsidNull.getBytes()).isEmpty();
+        assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidNull.toString()).isEmpty();
+
+        try {
+            WifiSsid.fromBytes(new byte[33]);
+            fail("Expected IllegalArgumentException for byte array length greater than 32.");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+    }
+
+    /**
+     * Verify the behavior of fromUtf8Text()
+     */
+    public void testfromUtf8Text() {
+        WifiSsid wifiSsidUtf8 = WifiSsid.fromUtf8Text(TEST_SSID_UTF_8);
+        assertThat(wifiSsidUtf8).isNotNull();
+        assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+        assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+        assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+        WifiSsid wifiSsidEmpty = WifiSsid.fromUtf8Text("");
+        assertThat(wifiSsidEmpty).isNotNull();
+        assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+        assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+        WifiSsid wifiSsidNull = WifiSsid.fromUtf8Text(null);
+        assertThat(wifiSsidNull).isNotNull();
+        assertThat(wifiSsidNull.getBytes()).isEmpty();
+        assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidNull.toString()).isEmpty();
+
+        try {
+            WifiSsid.fromUtf8Text("This is an SSID that is much longer than 32 bytes");
+            fail("Expected IllegalArgumentException for byte array length greater than 32.");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+    }
+
+    /**
+     * Verify the behavior of fromString()
+     */
+    public void testFromString() {
+        WifiSsid wifiSsidUtf8 = WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED);
+        assertThat(wifiSsidUtf8).isNotNull();
+        assertThat(wifiSsidUtf8.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+        assertThat(wifiSsidUtf8.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+        assertThat(wifiSsidUtf8.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+        WifiSsid wifiSsidUtf8Hex = WifiSsid.fromString(TEST_SSID_UTF_8_HEX);
+        assertThat(wifiSsidUtf8Hex).isNotNull();
+        assertThat(wifiSsidUtf8Hex.getBytes()).isEqualTo(TEST_SSID_UTF_8_BYTES);
+        assertThat(wifiSsidUtf8Hex.getUtf8Text()).isEqualTo(TEST_SSID_UTF_8);
+        assertThat(wifiSsidUtf8Hex.toString()).isEqualTo(TEST_SSID_UTF_8_QUOTED);
+
+        WifiSsid wifiSsidNonUtf8 = WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX);
+        assertThat(wifiSsidNonUtf8).isNotNull();
+        assertThat(wifiSsidNonUtf8.getBytes()).isEqualTo(TEST_SSID_NON_UTF_8_BYTES);
+        assertThat(wifiSsidNonUtf8.getUtf8Text()).isNull();
+        assertThat(wifiSsidNonUtf8.toString()).isEqualTo(TEST_SSID_NON_UTF_8_HEX);
+
+        WifiSsid wifiSsidEmpty = WifiSsid.fromUtf8Text("");
+        assertThat(wifiSsidEmpty).isNotNull();
+        assertThat(wifiSsidEmpty.getBytes()).isEmpty();
+        assertThat(wifiSsidEmpty.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidEmpty.toString()).isEmpty();
+
+        WifiSsid wifiSsidNull = WifiSsid.fromUtf8Text(null);
+        assertThat(wifiSsidNull).isNotNull();
+        assertThat(wifiSsidNull.getBytes()).isEmpty();
+        assertThat(wifiSsidNull.getUtf8Text().toString()).isEmpty();
+        assertThat(wifiSsidNull.toString()).isEmpty();
+
+        try {
+            WifiSsid.fromString("\"This is an SSID that is much longer than 32 bytes\"");
+            fail("Expected IllegalArgumentException for byte array length greater than 32.");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        try {
+            WifiSsid.fromString(
+                    "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789AB");
+            fail("Expected IllegalArgumentException for byte array length greater than 32.");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+
+        try {
+            WifiSsid.fromString("0123456");
+            fail("Expected IllegalArgumentException for odd-length hexadecimal string");
+        } catch (IllegalArgumentException e) {
+            // Success
+        }
+    }
+
+    /**
+     * Verify that SSID created from bytes, UTF-8 String, and toString()-formatted String with the
+     * same content are equal.
+     *
+     * @throws Exception
+     */
+    public void testEquals() throws Exception {
+        WifiSsid fromBytesUtf8 = WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES);
+        WifiSsid fromUtf8TextUtf8 = WifiSsid.fromUtf8Text(TEST_SSID_UTF_8);
+        WifiSsid fromStringUtf8 = WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED);
+        assertThat(fromBytesUtf8).isNotNull();
+        assertThat(fromUtf8TextUtf8).isNotNull();
+        assertThat(fromStringUtf8).isNotNull();
+        assertThat(fromBytesUtf8).isEqualTo(fromUtf8TextUtf8);
+        assertThat(fromBytesUtf8).isEqualTo(fromStringUtf8);
+        assertThat(fromUtf8TextUtf8).isEqualTo(fromStringUtf8);
+
+        WifiSsid fromBytesNonUtf8 = WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES);
+        WifiSsid fromStringNonUtf8 = WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX);
+        assertThat(fromBytesNonUtf8).isNotNull();
+        assertThat(fromStringNonUtf8).isNotNull();
+        assertThat(fromBytesNonUtf8).isEqualTo(fromStringNonUtf8);
+
+        assertThat(fromBytesUtf8).isNotEqualTo(fromBytesNonUtf8);
+    }
+
+    /**
+     * Verify the behavior of the Parcelable interface implementation.
+     */
+    public void testParcelable() throws Exception {
+        List<WifiSsid> testWifiSsids = Arrays.asList(
+                WifiSsid.fromBytes(TEST_SSID_UTF_8_BYTES),
+                WifiSsid.fromBytes(TEST_SSID_NON_UTF_8_BYTES),
+                WifiSsid.fromUtf8Text(TEST_SSID_UTF_8),
+                WifiSsid.fromString(TEST_SSID_UTF_8_QUOTED),
+                WifiSsid.fromString(TEST_SSID_UTF_8_HEX),
+                WifiSsid.fromString(TEST_SSID_NON_UTF_8_HEX));
+
+        for (WifiSsid wifiSsid : testWifiSsids) {
+            Parcel parcel = Parcel.obtain();
+            wifiSsid.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            assertThat(WifiSsid.CREATOR.createFromParcel(parcel)).isEqualTo(wifiSsid);
+        }
+    }
+}
diff --git a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
index a8866f2..b488fec 100644
--- a/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/rtt/cts/WifiRttTest.java
@@ -24,6 +24,7 @@
 import android.net.wifi.cts.WifiBuildCompat;
 import android.net.wifi.rtt.RangingRequest;
 import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.ResponderConfig;
 import android.net.wifi.rtt.ResponderLocation;
 import android.platform.test.annotations.AppModeFull;
 
@@ -47,7 +48,7 @@
     private static final int NUM_OF_RTT_ITERATIONS = 10;
 
     // Maximum failure rate of RTT measurements (percentage)
-    private static final int MAX_FAILURE_RATE_PERCENT = 10;
+    private static final int MAX_FAILURE_RATE_PERCENT = 20;
 
     // Maximum variation from the average measurement (measures consistency)
     private static final int MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM = 2000;
@@ -55,6 +56,9 @@
     // Maximum failure rate of one-sided RTT measurements (percentage)
     private static final int MAX_NON11MC_FAILURE_RATE_PERCENT = 40;
 
+    // Maximum non-8011mc variation from the average measurement (measures consistency)
+    private static final int MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM = 4000;
+
     // Minimum valid RSSI value
     private static final int MIN_VALID_RSSI = -100;
 
@@ -65,14 +69,14 @@
     private static final int intervalMs = 200;
 
     /**
-     * Test Wi-Fi RTT ranging operation:
+     * Test Wi-Fi RTT ranging operation using ScanResults in request:
      * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
      * - Perform N (constant) RTT operations
      * - Validate:
      *   - Failure ratio < threshold (constant)
      *   - Result margin < threshold (constant)
      */
-    public void testRangingToTest11mcAp() throws InterruptedException {
+    public void testRangingToTest11mcApUsingScanResult() throws InterruptedException {
         if (!shouldTestWifiRtt(getContext())) {
             return;
         }
@@ -82,10 +86,10 @@
         assertNotNull(
                 "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
                         + "your test setup includes them!", testAp);
-
         // Perform RTT operations
         RangingRequest.Builder builder = new RangingRequest.Builder();
         builder.addAccessPoint(testAp);
+
         if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
             builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
             assertTrue(RangingRequest.getDefaultRttBurstSize()
@@ -93,11 +97,124 @@
             assertTrue(RangingRequest.getDefaultRttBurstSize()
                     <= RangingRequest.getMaxRttBurstSize());
         }
+
         RangingRequest request = builder.build();
         if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
             assertEquals(1, request.getRttResponders().size());
         }
+        range11mcApRequest(request, testAp);
+    }
 
+    /**
+     * Test Wi-Fi RTT ranging using ResponderConfig in the single responder RangingRequest API.
+     * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
+     * - Perform N (constant) RTT operations
+     * - Validate:
+     *   - Failure ratio < threshold (constant)
+     *   - Result margin < threshold (constant)
+     */
+    public void testRangingToTest11mcApUsingResponderConfig() throws InterruptedException {
+        if (!shouldTestWifiRtt(getContext())) {
+            return;
+        }
+
+        // Scan for IEEE 802.11mc supporting APs
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        assertNotNull(
+                "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+                        + "your test setup includes them!", testAp);
+        int preamble = ResponderConfig.fromScanResult(testAp).getPreamble();
+
+        // Create a ResponderConfig from the builder API.
+        ResponderConfig.Builder responderBuilder = new ResponderConfig.Builder();
+        ResponderConfig responder = responderBuilder
+                .setMacAddress(MacAddress.fromString(testAp.BSSID))
+                .set80211mcSupported(testAp.is80211mcResponder())
+                .setChannelWidth(testAp.channelWidth)
+                .setFrequencyMhz(testAp.frequency)
+                .setCenterFreq0Mhz(testAp.centerFreq0)
+                .setCenterFreq1Mhz(testAp.centerFreq1)
+                .setPreamble(preamble)
+                .build();
+
+        // Validate ResponderConfig.Builder set method arguments match getter methods.
+        assertTrue(responder.getMacAddress().toString().equalsIgnoreCase(testAp.BSSID)
+                        && responder.is80211mcSupported() == testAp.is80211mcResponder()
+                        && responder.getChannelWidth() == testAp.channelWidth
+                        && responder.getFrequencyMhz() == testAp.frequency
+                        && responder.getCenterFreq0Mhz() == testAp.centerFreq0
+                        && responder.getCenterFreq1Mhz() == testAp.centerFreq1
+                        && responder.getPreamble() == preamble);
+
+        // Perform RTT operations
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addResponder(responder);
+
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+            builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+            assertTrue(RangingRequest.getDefaultRttBurstSize()
+                    >= RangingRequest.getMinRttBurstSize());
+            assertTrue(RangingRequest.getDefaultRttBurstSize()
+                    <= RangingRequest.getMaxRttBurstSize());
+        }
+
+        RangingRequest request = builder.build();
+
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+            assertEquals(1, request.getRttResponders().size());
+        }
+        range11mcApRequest(request, testAp);
+    }
+
+    /**
+     * Test Wi-Fi RTT ranging using ResponderConfig in the multi-Responder RangingRequest API.
+     * - Scan for visible APs for the test AP (which is validated to support IEEE 802.11mc)
+     * - Perform N (constant) RTT operations
+     * - Validate:
+     *   - Failure ratio < threshold (constant)
+     *   - Result margin < threshold (constant)
+     */
+    public void testRangingToTest11mcApUsingListResponderConfig() throws InterruptedException {
+        if (!shouldTestWifiRtt(getContext())) {
+            return;
+        }
+
+        // Scan for IEEE 802.11mc supporting APs
+        ScanResult testAp = scanForTest11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        assertNotNull(
+                "Cannot find any test APs which support RTT / IEEE 802.11mc - please verify that "
+                        + "your test setup includes them!", testAp);
+        ResponderConfig responder = ResponderConfig.fromScanResult(testAp);
+        // Perform RTT operations
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        List<ResponderConfig> responders = new ArrayList<>();
+        responders.add(responder);
+        builder.addResponders(responders);
+
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+            builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+            assertTrue(RangingRequest.getDefaultRttBurstSize()
+                    >= RangingRequest.getMinRttBurstSize());
+            assertTrue(RangingRequest.getDefaultRttBurstSize()
+                    <= RangingRequest.getMaxRttBurstSize());
+        }
+
+        RangingRequest request = builder.build();
+
+        if (WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+            assertEquals(1, request.getRttResponders().size());
+        }
+        range11mcApRequest(request, testAp);
+    }
+
+    /**
+     * Utility method for validating a ranging request.
+     *
+     * @param request the ranging request that is being tested
+     * @param testAp the original test scan result to provide feedback on failure conditions
+     */
+    private void range11mcApRequest(RangingRequest request, ScanResult testAp)
+            throws InterruptedException {
         List<RangingResult> allResults = new ArrayList<>();
         int numFailures = 0;
         int distanceSum = 0;
@@ -217,6 +334,8 @@
         }
     }
 
+
+
     /**
      * Validate that when a request contains more range operations than allowed (by API) that we
      * get an exception.
@@ -451,7 +570,7 @@
     }
 
     /**
-     * Test Wi-Fi One-sided RTT ranging operation:
+     * Test Wi-Fi One-sided RTT ranging operation using ScanResult in request:
      * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
      * - in the 5GHz band.
      * - Perform N (constant) RTT operations
@@ -460,7 +579,7 @@
      *   - Failure ratio < threshold (constant)
      *   - Result margin < threshold (constant)
      */
-    public void testRangingToTestNon11mcAp() throws InterruptedException {
+    public void testRangingToTestNon11mcApUsingScanResult() throws InterruptedException {
         if (!shouldTestWifiRtt(getContext())
                 || !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
             return;
@@ -478,6 +597,52 @@
         builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
         RangingRequest request = builder.build();
 
+        // Perform the rquest
+        rangeNon11mcApRequest(request, testAp, MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+    }
+
+    /**
+     * Test Wi-Fi one-sided RTT ranging operation using ResponderConfig in request:
+     * - Scan for visible APs for the test AP (which do not support IEEE 802.11mc) and are operating
+     * - in the 5GHz band.
+     * - Perform N (constant) RTT operations
+     * - Remove outliers while insuring greater than 50% of the results still remain
+     * - Validate:
+     *   - Failure ratio < threshold (constant)
+     *   - Result margin < threshold (constant)
+     */
+    public void testRangingToTestNon11mcApUsingResponderConfig() throws InterruptedException {
+        if (!shouldTestWifiRtt(getContext())
+                || !WifiBuildCompat.isPlatformOrWifiModuleAtLeastS(getContext())) {
+            return;
+        }
+
+        // Scan for Non-IEEE 802.11mc supporting APs
+        ScanResult testAp = scanForTestNon11mcCapableAp(NUM_SCANS_SEARCHING_FOR_IEEE80211MC_AP);
+        assertNotNull(
+                "Cannot find any test APs which are Non-IEEE 802.11mc - please verify that"
+                        + " your test setup includes them!", testAp);
+
+        ResponderConfig responder = ResponderConfig.fromScanResult(testAp);
+
+        // Perform RTT operations
+        RangingRequest.Builder builder = new RangingRequest.Builder();
+        builder.addResponder(responder);
+        builder.setRttBurstSize(RangingRequest.getMaxRttBurstSize());
+        RangingRequest request = builder.build();
+
+        // Perform the rquest
+        rangeNon11mcApRequest(request, testAp, MAX_NON11MC_VARIATION_FROM_AVERAGE_DISTANCE_MM);
+    }
+
+    /**
+     * Utility method for validating a ranging request to a non-80211mc AP.
+     *
+     * @param request the ranging request that is being tested
+     * @param testAp the original test scan result to provide feedback on failure conditions
+     */
+    private void rangeNon11mcApRequest(RangingRequest request, ScanResult testAp,
+            int variationLimit) throws InterruptedException {
         List<RangingResult> allResults = new ArrayList<>();
         int numFailures = 0;
         int distanceSum = 0;
@@ -571,55 +736,59 @@
                 ResultType.NEUTRAL, ResultUnit.NONE);
         reportLog.submit();
 
-        /** TODO(b/192909380): enable the performance verification after device fix.
-            // Analyze results
-            assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures
-                            + ", ITERATIONS="
-                            + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
-                            + ", AP SSID=" + testAp.SSID,
-                    numFailures <= NUM_OF_RTT_ITERATIONS * MAX_NON11MC_FAILURE_RATE_PERCENT / 100);
+        // This bug below has been addressed by making the test parameters for Non-80211mc devices
+        // less stringent. Please update the bug if this does not solve the problem.
+        // TODO(b/192909380): enable the performance verification after device fix.
 
-            if (numFailures != NUM_OF_RTT_ITERATIONS) {
-                // Calculate an initial average using all measurements to determine distance outliers
-                double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
-                // Now figure out the distance outliers and mark them in the distance inclusion map
-                int validDistances = 0;
-                for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
-                    if (distanceMms[i] - MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM < distanceAvg) {
-                        // Distances that are in range for the distribution are included in the map
-                        distanceInclusionMap[i] = true;
-                        validDistances++;
-                    } else {
-                        // Distances that are out of range for the distribution are excluded in the map
-                        distanceInclusionMap[i] = false;
-                    }
-                }
+        // Analyze results
+        assertTrue("Wi-Fi RTT failure rate exceeds threshold: FAIL=" + numFailures
+                        + ", ITERATIONS="
+                        + NUM_OF_RTT_ITERATIONS + ", AP RSSI=" + testAp.level
+                        + ", AP SSID=" + testAp.SSID,
+                numFailures <= NUM_OF_RTT_ITERATIONS * MAX_NON11MC_FAILURE_RATE_PERCENT / 100);
 
-                assertTrue("After fails+outlier removal greater that 50% distances must remain: " +
-                        NUM_OF_RTT_ITERATIONS / 2, validDistances > NUM_OF_RTT_ITERATIONS / 2);
+        if (numFailures != NUM_OF_RTT_ITERATIONS) {
+            // Calculate an initial average using all measurements to determine distance outliers
+            double distanceAvg = (double) distanceSum / (NUM_OF_RTT_ITERATIONS - numFailures);
+            // Now figure out the distance outliers and mark them in the distance inclusion map
+            int validDistances = 0;
+            for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+                if (distanceMms[i] - variationLimit < distanceAvg) {
+                    // Distances that are in range for the distribution are included in the map
+                    distanceInclusionMap[i] = true;
+                    validDistances++;
+                } else {
+                    // Distances that are out of range for the distribution are excluded in the map
+                    distanceInclusionMap[i] = false;
+                }
+            }
 
-                // Remove the distance outliers and find the new average, min and max.
-                distanceSum = 0;
-                distanceMax = Integer.MIN_VALUE;
-                distanceMin = Integer.MAX_VALUE;
-                for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
-                    if (distanceInclusionMap[i]) {
-                        distanceSum += distanceMms[i];
-                        distanceMin = Math.min(distanceMin, distanceMms[i]);
-                        distanceMax = Math.max(distanceMax, distanceMms[i]);
-                    }
+            assertTrue("After fails+outlier removal greater that 50% distances must remain: "
+                    + NUM_OF_RTT_ITERATIONS / 2, validDistances > NUM_OF_RTT_ITERATIONS / 2);
+
+            // Remove the distance outliers and find the new average, min and max.
+            distanceSum = 0;
+            distanceMax = Integer.MIN_VALUE;
+            distanceMin = Integer.MAX_VALUE;
+            for (int i = 0; i < (NUM_OF_RTT_ITERATIONS - numFailures); i++) {
+                if (distanceInclusionMap[i]) {
+                    distanceSum += distanceMms[i];
+                    distanceMin = Math.min(distanceMin, distanceMms[i]);
+                    distanceMax = Math.max(distanceMax, distanceMms[i]);
                 }
-                distanceAvg = (double) distanceSum / validDistances;
-                assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
-                                + (distanceMax - distanceAvg),
-                        (distanceMax - distanceAvg) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
-                assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
-                                + (distanceAvg - distanceMin),
-                        (distanceAvg - distanceMin) <= MAX_VARIATION_FROM_AVERAGE_DISTANCE_MM);
-                for (int i = 0; i < numGoodResults; ++i) {
-                    assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
-                    assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
-                }
-         */
+            }
+            distanceAvg = (double) distanceSum / validDistances;
+            assertTrue("Wi-Fi RTT: Variation (max direction) exceeds threshold, Variation ="
+                            + (distanceMax - distanceAvg),
+                    (distanceMax - distanceAvg) <= variationLimit);
+            assertTrue("Wi-Fi RTT: Variation (min direction) exceeds threshold, Variation ="
+                            + (distanceAvg - distanceMin),
+                    (distanceAvg - distanceMin) <= variationLimit);
+            for (int i = 0; i < numGoodResults; ++i) {
+                assertNotSame("Number of attempted measurements is 0", 0, numAttempted[i]);
+                assertNotSame("Number of successful measurements is 0", 0, numSuccessful[i]);
+            }
+        }
+
     }
 }
diff --git a/tests/translation/AndroidManifest.xml b/tests/translation/AndroidManifest.xml
index b20fe08..1ed4560 100644
--- a/tests/translation/AndroidManifest.xml
+++ b/tests/translation/AndroidManifest.xml
@@ -18,6 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.translation.cts">
 
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <application android:label="Translation TestCase">
         <uses-library android:name="android.test.runner"/>
 
diff --git a/tests/translation/AndroidTest.xml b/tests/translation/AndroidTest.xml
index 6f213f5..e113244 100644
--- a/tests/translation/AndroidTest.xml
+++ b/tests/translation/AndroidTest.xml
@@ -41,4 +41,10 @@
     <!-- dismiss all system dialogs before launch test -->
     <option name="run-command" value="am broadcast -a android.intent.action.CLOSE_SYSTEM_DIALOGS" />
   </target_preparer>
+  <!-- Collect the files generated on error -->
+  <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+    <option name="directory-keys" value="/sdcard/CtsTranslationTestCases" />
+    <option name="collect-on-run-ended-only" value="true" />
+    <option name="clean-up" value="false" />
+  </metrics_collector>
 </configuration>
diff --git a/tests/translation/src/android/translation/cts/CtsTestIme.java b/tests/translation/src/android/translation/cts/CtsTestIme.java
index b07be05..8533989 100644
--- a/tests/translation/src/android/translation/cts/CtsTestIme.java
+++ b/tests/translation/src/android/translation/cts/CtsTestIme.java
@@ -194,7 +194,7 @@
             filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_FINISH);
             filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_RESUME);
             filter.addAction(ACTION_ASSERT_UI_TRANSLATION_CALLBACK_ON_PAUSE);
-            mContext.registerReceiver(this, filter);
+            mContext.registerReceiver(this, filter, Context.RECEIVER_NOT_EXPORTED);
         }
 
         void unRegister() {
diff --git a/tests/translation/src/android/translation/cts/CtsTranslationService.java b/tests/translation/src/android/translation/cts/CtsTranslationService.java
index 49f70cd..b7d50d5 100644
--- a/tests/translation/src/android/translation/cts/CtsTranslationService.java
+++ b/tests/translation/src/android/translation/cts/CtsTranslationService.java
@@ -65,6 +65,8 @@
 
     private final CountDownLatch mSessionDestroyedLatch = new CountDownLatch(1);
 
+    private TranslationContext mTranslationContext;
+
     /**
      * Timeout for Translation cts.
      */
@@ -109,6 +111,7 @@
     public void onCreateTranslationSession(@NonNull TranslationContext translationContext,
             int sessionId, @NonNull Consumer<Boolean> callback) {
         Log.v(TAG, "onCreateTranslationSession");
+        mTranslationContext = translationContext;
         callback.accept(true);
     }
 
@@ -116,6 +119,7 @@
     public void onFinishTranslationSession(int sessionId) {
         Log.v(TAG, "onFinishTranslationSession");
         mSessionDestroyedLatch.countDown();
+        mTranslationContext = null;
     }
 
     @Override
@@ -149,6 +153,10 @@
         return sServiceWatcher;
     }
 
+    TranslationContext getTranslationContext() {
+        return mTranslationContext;
+    }
+
     /**
      * Wait the Translation session destroyed.
      */
diff --git a/tests/translation/src/android/translation/cts/Helper.java b/tests/translation/src/android/translation/cts/Helper.java
index 12e4e66..0b4c7f8 100644
--- a/tests/translation/src/android/translation/cts/Helper.java
+++ b/tests/translation/src/android/translation/cts/Helper.java
@@ -18,18 +18,29 @@
 
 import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
 
+import android.app.Instrumentation;
+import android.app.UiAutomation;
 import android.content.ContentCaptureOptions;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.os.UserHandle;
 import android.util.Log;
-import android.view.contentcapture.ContentCaptureContext;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
+import androidx.test.core.app.ApplicationProvider;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiDevice;
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import com.android.compatibility.common.util.BitmapUtils;
+import com.android.compatibility.common.util.TestNameUtils;
+
+import java.io.File;
+import java.io.IOException;
+
 /**
  * Helper for common funcionalities.
  */
@@ -58,6 +69,7 @@
     public static final String EXTRA_VERIFY_RESULT = "verify_result";
 
     public static final String CUSTOM_TRANSLATION_ID_MY_TAG = "myTag";
+    public static final String LOCAL_TEST_FILES_DIR = "/sdcard/CtsTranslationTestCases";
     private static final String LOG_TAG = "log.tag.UiTranslation";
 
     /**
@@ -166,4 +178,69 @@
         Log.d(TAG, "disableDebugLog(), set level  " + level);
         System.setProperty(LOG_TAG, level);
     }
-}
\ No newline at end of file
+
+    // TODO: Move to a library that can be shared for smart os components.
+    /**
+     * Takes a screenshot and save it in the file system for analysis.
+     */
+    public static void takeScreenshotAndSave(Context context, String testName,
+            String targetFolder) {
+        File file = null;
+        try {
+            file = createTestFile(testName,"sreenshot.png", targetFolder);
+            if (file != null) {
+                Log.i(TAG, "Taking screenshot on " + file);
+                final Bitmap screenshot = takeScreenshot();
+                saveBitmapToFile(screenshot, file);
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Error taking screenshot and saving on " + file, e);
+        }
+    }
+
+    public static File saveBitmapToFile(Bitmap bitmap, File file) {
+        Log.i(TAG, "Saving bitmap at " + file);
+        BitmapUtils.saveBitmap(bitmap, file.getParent(), file.getName());
+        return file;
+    }
+
+    private static Bitmap takeScreenshot() {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        UiAutomation automan = instrumentation.getUiAutomation();
+        final Bitmap bitmap = automan.takeScreenshot();
+        return bitmap;
+    }
+
+    public static File createTestFile(String testName, String name, String targetFolder)
+            throws IOException {
+        final File dir = getLocalDirectory(targetFolder);
+        if (dir == null) return null;
+        final String prefix = testName.replaceAll("\\.|\\(|\\/", "_").replaceAll("\\)", "");
+        final String filename = prefix + "-" + name;
+
+        return createFile(dir, filename);
+    }
+
+    private static File getLocalDirectory(String targetFolder) {
+        final File dir = new File(targetFolder);
+        dir.mkdirs();
+        if (!dir.exists()) {
+            Log.e(TAG, "Could not create directory " + dir);
+            return null;
+        }
+        return dir;
+    }
+
+    private static File createFile(File dir, String filename) throws IOException {
+        final File file = new File(dir, filename);
+        if (file.exists()) {
+            Log.v(TAG, "Deleting file " + file);
+            file.delete();
+        }
+        if (!file.createNewFile()) {
+            Log.e(TAG, "Could not create file " + file);
+            return null;
+        }
+        return file;
+    }
+}
diff --git a/tests/translation/src/android/translation/cts/TranslationManagerTest.java b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
index 128f5a8..fa21041 100644
--- a/tests/translation/src/android/translation/cts/TranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/TranslationManagerTest.java
@@ -257,6 +257,12 @@
 
         sTranslationReplier.getNextTranslationRequest();
 
+        CtsTranslationService translationService =
+                mServiceWatcher.getService();
+        TranslationContext sessionContext = translationService.getTranslationContext();
+        assertThat(sessionContext).isNotNull();
+        assertThat(sessionContext.getActivityId()).isNull();
+
         translator.destroy();
         assertThat(translator.isDestroyed()).isTrue();
         try {
diff --git a/tests/translation/src/android/translation/cts/TranslationTestWatcher.java b/tests/translation/src/android/translation/cts/TranslationTestWatcher.java
new file mode 100644
index 0000000..ba80df2
--- /dev/null
+++ b/tests/translation/src/android/translation/cts/TranslationTestWatcher.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 android.translation.cts;
+
+import android.util.Log;
+
+import com.android.compatibility.common.util.TestNameUtils;
+
+import org.junit.rules.TestWatcher;
+import org.junit.runner.Description;
+
+/**
+ * Custom {@link TestWatcher} that  used for UiTranslationManagerTest.
+ */
+public final class TranslationTestWatcher extends TestWatcher {
+
+    private static final String TAG = "TranslationTestWatcher";
+
+    @Override
+    protected void starting(Description description) {
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Starting " + testName);
+        TestNameUtils.setCurrentTestName(testName);
+    }
+
+    @Override
+    protected void finished(Description description) {
+        final String testName = description.getDisplayName();
+        Log.i(TAG, "Finished " + testName);
+        TestNameUtils.setCurrentTestName(null);
+    }
+}
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 34ceb72..0f82b87 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -57,6 +57,7 @@
 import android.view.autofill.AutofillId;
 import android.view.contentcapture.ContentCaptureContext;
 import android.view.inputmethod.InputMethodManager;
+import android.view.translation.TranslationContext;
 import android.view.translation.TranslationRequest;
 import android.view.translation.TranslationResponse;
 import android.view.translation.TranslationResponseValue;
@@ -79,6 +80,7 @@
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 import com.android.compatibility.common.util.PollingCheck;
 import com.android.compatibility.common.util.RequiredServiceRule;
+import com.android.compatibility.common.util.TestNameUtils;
 
 import org.junit.After;
 import org.junit.AfterClass;
@@ -113,7 +115,7 @@
 
     private static final String TAG = "UiTranslationManagerTest";
 
-    private static final long UI_WAIT_TIMEOUT = 2000;
+    private static final long UI_WAIT_TIMEOUT = 2500;
 
     // TODO: Use fw definition when it becomes public or testapi
     private static final String ID_CONTENT_DESCRIPTION = "android:content_description";
@@ -141,6 +143,9 @@
     public final RequiredServiceRule mTranslationServiceRule =
             new RequiredServiceRule(TRANSLATION_MANAGER_SERVICE);
 
+    @Rule
+    public final TranslationTestWatcher mTranslationTestWatcher = new TranslationTestWatcher();
+
     @BeforeClass
     public static void oneTimeSetup() {
         sContext = ApplicationProvider.getApplicationContext();
@@ -178,68 +183,81 @@
 
     @Test
     public void testUiTranslation() throws Throwable {
-        final Pair<List<AutofillId>, ContentCaptureContext> result =
-                enableServicesAndStartActivityForTranslation();
+        try {
+            final Pair<List<AutofillId>, ContentCaptureContext> result =
+                    enableServicesAndStartActivityForTranslation();
 
-        final CharSequence originalText = mTextView.getText();
-        final List<AutofillId> views = result.first;
-        final ContentCaptureContext contentCaptureContext = result.second;
+            final CharSequence originalText = mTextView.getText();
+            final List<AutofillId> views = result.first;
+            final ContentCaptureContext contentCaptureContext = result.second;
 
-        final String translatedText = "success";
-        final UiObject2 helloText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
-                SimpleActivity.HELLO_TEXT_ID);
-        assertThat(helloText).isNotNull();
-        // Set response
-        final TranslationResponse response = createViewsTranslationResponse(views, translatedText);
-        sTranslationReplier.addResponse(response);
+            final String translatedText = "success";
+            final UiObject2 helloText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+                    SimpleActivity.HELLO_TEXT_ID);
+            assertThat(helloText).isNotNull();
+            // Set response
+            final TranslationResponse response =
+                    createViewsTranslationResponse(views, translatedText);
+            sTranslationReplier.addResponse(response);
 
-        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
 
-        // Check request
-        final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
-        final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
-        final ViewTranslationRequest viewRequest = requests.get(0);
-        assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
-        assertThat(viewRequest.getKeys().size()).isEqualTo(1);
-        assertThat(viewRequest.getKeys()).containsExactly(ViewTranslationRequest.ID_TEXT);
-        assertThat(viewRequest.getValue(ViewTranslationRequest.ID_TEXT).getText())
-                .isEqualTo(originalText.toString());
+            // Check request
+            final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
+            final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
+            final ViewTranslationRequest viewRequest = requests.get(0);
+            assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
+            assertThat(viewRequest.getKeys().size()).isEqualTo(1);
+            assertThat(viewRequest.getKeys()).containsExactly(ViewTranslationRequest.ID_TEXT);
+            assertThat(viewRequest.getValue(ViewTranslationRequest.ID_TEXT).getText())
+                    .isEqualTo(originalText.toString());
+            CtsTranslationService translationService =
+                    mTranslationServiceServiceWatcher.getService();
+            TranslationContext translationContext = translationService.getTranslationContext();
+            assertThat(translationContext).isNotNull();
+            assertThat(translationContext.getActivityId()).isNotNull();
+            assertThat(translationContext.getActivityId())
+                    .isEqualTo(contentCaptureContext.getActivityId());
 
-        assertThat(helloText.getText()).isEqualTo(translatedText);
-        assertThat(mTextView.getViewTranslationResponse())
-                .isEqualTo(response.getViewTranslationResponses().get(0));
+            assertThat(helloText.getText()).isEqualTo(translatedText);
+            assertThat(mTextView.getViewTranslationResponse())
+                    .isEqualTo(response.getViewTranslationResponses().get(0));
 
-        pauseUiTranslation(contentCaptureContext);
+            pauseUiTranslation(contentCaptureContext);
 
-        assertThat(helloText.getText()).isEqualTo(originalText.toString());
+            assertThat(helloText.getText()).isEqualTo(originalText.toString());
 
-        resumeUiTranslation(contentCaptureContext);
+            resumeUiTranslation(contentCaptureContext);
 
-        assertThat(helloText.getText()).isEqualTo(translatedText);
+            assertThat(helloText.getText()).isEqualTo(translatedText);
 
-        finishUiTranslation(contentCaptureContext);
+            finishUiTranslation(contentCaptureContext);
 
-        assertThat(helloText.getText()).isEqualTo(originalText.toString());
+            assertThat(helloText.getText()).isEqualTo(originalText.toString());
 
-        // Check the Translation session is destroyed after calling finishTranslation()
-        CtsTranslationService translationService =
-                mTranslationServiceServiceWatcher.getService();
-        translationService.awaitSessionDestroyed();
+            // Check the Translation session is destroyed after calling finishTranslation()
+            translationService.awaitSessionDestroyed();
 
-        // Test re-translating.
-        sTranslationReplier.addResponse(createViewsTranslationResponse(views, translatedText));
+            // Test re-translating.
+            sTranslationReplier.addResponse(createViewsTranslationResponse(views, translatedText));
 
-        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
 
-        assertThat(helloText.getText()).isEqualTo(translatedText);
+            assertThat(helloText.getText()).isEqualTo(translatedText);
 
-        // Also make sure pausing still works.
-        pauseUiTranslation(contentCaptureContext);
+            // Also make sure pausing still works.
+            pauseUiTranslation(contentCaptureContext);
 
-        assertThat(helloText.getText()).isEqualTo(originalText.toString());
+            assertThat(helloText.getText()).isEqualTo(originalText.toString());
+        } catch (Throwable t) {
+            Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+                    Helper.LOCAL_TEST_FILES_DIR);
+            throw t;
+        }
     }
 
     @Test
+    @FlakyTest(bugId = 192418800)
     public void testPauseUiTranslationThenStartUiTranslation() throws Throwable {
         final Pair<List<AutofillId>, ContentCaptureContext> result =
                 enableServicesAndStartActivityForTranslation();
@@ -321,82 +339,94 @@
     @Test
     @FlakyTest(bugId = 192418800)
     public void testUiTranslation_ViewTranslationCallback_paddingText() throws Throwable {
-        final Pair<List<AutofillId>, ContentCaptureContext> result =
-                enableServicesAndStartActivityForTranslation();
-        final List<AutofillId> views = result.first;
-        final ContentCaptureContext contentCaptureContext = result.second;
+        try {
+            final Pair<List<AutofillId>, ContentCaptureContext> result =
+                    enableServicesAndStartActivityForTranslation();
+            final List<AutofillId> views = result.first;
+            final ContentCaptureContext contentCaptureContext = result.second;
 
-        // Set response
-        final CharSequence originalText = mTextView.getText();
-        final CharSequence translatedText = "Translated World";
-        sTranslationReplier.addResponse(
-                createViewsTranslationResponse(views, translatedText.toString()));
+            // Set response
+            final CharSequence originalText = mTextView.getText();
+            final CharSequence translatedText = "Translated World";
+            sTranslationReplier.addResponse(
+                    createViewsTranslationResponse(views, translatedText.toString()));
 
-        // Use TextView default ViewTranslationCallback implementation
-        startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
+            // Use TextView default ViewTranslationCallback implementation
+            startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
 
-        CharSequence currentText = mTextView.getText();
-        assertThat(currentText.length()).isNotEqualTo(originalText.length());
-        assertThat(currentText.length()).isEqualTo(translatedText.length());
+            CharSequence currentText = mTextView.getText();
+            assertThat(currentText.length()).isNotEqualTo(originalText.length());
+            assertThat(currentText.length()).isEqualTo(translatedText.length());
 
-        finishUiTranslation(contentCaptureContext);
+            finishUiTranslation(contentCaptureContext);
 
-        // Set Customized ViewTranslationCallback
-        ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
-        mTextView.setViewTranslationCallback(mockCallback);
+            // Set Customized ViewTranslationCallback
+            ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
+            mTextView.setViewTranslationCallback(mockCallback);
 
-        startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
+            startUiTranslation(/* shouldPadContent */ true, views, contentCaptureContext);
 
-        assertThat(mTextView.getText().length()).isEqualTo(originalText.length());
+            assertThat(mTextView.getText().length()).isEqualTo(originalText.length());
+        } catch (Throwable t) {
+            Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+                    Helper.LOCAL_TEST_FILES_DIR);
+            throw t;
+        }
     }
 
     @Test
     public void testUiTranslation_hasContentDescription() throws Throwable {
-        final Pair<List<AutofillId>, ContentCaptureContext> result =
-                enableServicesAndStartActivityForTranslation();
-        final List<AutofillId> views = result.first;
-        final ContentCaptureContext contentCaptureContext = result.second;
+        try {
+            final Pair<List<AutofillId>, ContentCaptureContext> result =
+                    enableServicesAndStartActivityForTranslation();
+            final List<AutofillId> views = result.first;
+            final ContentCaptureContext contentCaptureContext = result.second;
 
-        // Set response
-        final CharSequence translatedText = "Translated World";
-        final CharSequence originalDescription = "Hello Description";
-        mActivityScenario.onActivity(activity -> {
-            mTextView.setContentDescription(originalDescription);
-        });
-        sTranslationReplier.addResponse(
-                createViewsTranslationResponse(views, translatedText.toString()));
+            // Set response
+            final CharSequence translatedText = "Translated World";
+            final CharSequence originalDescription = "Hello Description";
+            mActivityScenario.onActivity(activity -> {
+                mTextView.setContentDescription(originalDescription);
+            });
+            sTranslationReplier.addResponse(
+                    createViewsTranslationResponse(views, translatedText.toString()));
 
-        // Use TextView default ViewTranslationCallback implementation
-        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+            // Use TextView default ViewTranslationCallback implementation
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
 
-        assertThat(mTextView.getContentDescription().toString())
-                .isEqualTo(translatedText.toString());
+            assertThat(mTextView.getContentDescription().toString())
+                    .isEqualTo(translatedText.toString());
 
-        // Check request to make sure the content description key doesn't be changed
-        final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
-        final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
-        final ViewTranslationRequest viewRequest = requests.get(0);
-        assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
-        assertThat(viewRequest.getKeys().size()).isEqualTo(2);
-        assertThat(viewRequest.getKeys()).containsExactly(ID_CONTENT_DESCRIPTION,
-                ViewTranslationRequest.ID_TEXT);
-        assertThat(viewRequest.getValue(ID_CONTENT_DESCRIPTION).getText())
-                .isEqualTo(originalDescription);
+            // Check request to make sure the content description key doesn't be changed
+            final TranslationRequest request = sTranslationReplier.getNextTranslationRequest();
+            final List<ViewTranslationRequest> requests = request.getViewTranslationRequests();
+            final ViewTranslationRequest viewRequest = requests.get(0);
+            assertThat(viewRequest.getAutofillId()).isEqualTo(views.get(0));
+            assertThat(viewRequest.getKeys().size()).isEqualTo(2);
+            assertThat(viewRequest.getKeys()).containsExactly(ID_CONTENT_DESCRIPTION,
+                    ViewTranslationRequest.ID_TEXT);
+            assertThat(viewRequest.getValue(ID_CONTENT_DESCRIPTION).getText())
+                    .isEqualTo(originalDescription);
 
-        pauseUiTranslation(contentCaptureContext);
+            pauseUiTranslation(contentCaptureContext);
 
-        assertThat(mTextView.getContentDescription().toString())
-                .isEqualTo(originalDescription.toString());
+            assertThat(mTextView.getContentDescription().toString())
+                    .isEqualTo(originalDescription.toString());
 
-        resumeUiTranslation(contentCaptureContext);
+            resumeUiTranslation(contentCaptureContext);
 
-        assertThat(mTextView.getContentDescription().toString())
-                .isEqualTo(translatedText.toString());
+            assertThat(mTextView.getContentDescription().toString())
+                    .isEqualTo(translatedText.toString());
 
-        finishUiTranslation(contentCaptureContext);
+            finishUiTranslation(contentCaptureContext);
 
-        assertThat(mTextView.getContentDescription().toString())
-                .isEqualTo(originalDescription.toString());
+            assertThat(mTextView.getContentDescription().toString())
+                    .isEqualTo(originalDescription.toString());
+        } catch (Throwable t) {
+            Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+                    Helper.LOCAL_TEST_FILES_DIR);
+            throw t;
+        }
     }
 
     @Test
@@ -587,68 +617,80 @@
 
     @Test
     public void testUiTranslation_translationResponseNotSetForCustomTextView() throws Throwable {
-        // Enable CTS ContentCaptureService
-        CtsContentCaptureService contentcaptureService = enableContentCaptureService();
-        // Start Activity and get needed information
-        final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
+        try {
+            // Enable CTS ContentCaptureService
+            CtsContentCaptureService contentcaptureService = enableContentCaptureService();
+            // Start Activity and get needed information
+            final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
 
-        // Wait session created and get the ConttCaptureContext from ContentCaptureService
-        final ContentCaptureContext contentCaptureContext =
-                getContentCaptureContextFromContentCaptureService(contentcaptureService);
+            // Wait session created and get the ConttCaptureContext from ContentCaptureService
+            final ContentCaptureContext contentCaptureContext =
+                    getContentCaptureContextFromContentCaptureService(contentcaptureService);
 
-        // enable CTS TranslationService
-        mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
-        Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
+            // enable CTS TranslationService
+            mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
+            Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
 
-        // Set response
-        final TranslationResponse expectedResponse =
-                createViewsTranslationResponse(views, "success");
-        sTranslationReplier.addResponse(expectedResponse);
+            // Set response
+            final TranslationResponse expectedResponse =
+                    createViewsTranslationResponse(views, "success");
+            sTranslationReplier.addResponse(expectedResponse);
 
-        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
 
-        // Verify result. Translation response doesn't set, it should show original text
-        assertThat(mResponseNotSetTextView.getSavedResponse()).isNotNull();
-        final UiObject2 responseNotSetText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
-                CustomTextViewActivity.ID_RESPONSE_NOT_SET_TEXT);
-        assertThat(responseNotSetText).isNotNull();
-        assertThat(responseNotSetText.getText()).isEqualTo("Hello World 1");
+            // Verify result. Translation response doesn't set, it should show original text
+            assertThat(mResponseNotSetTextView.getSavedResponse()).isNotNull();
+            final UiObject2 responseNotSetText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+                    CustomTextViewActivity.ID_RESPONSE_NOT_SET_TEXT);
+            assertThat(responseNotSetText).isNotNull();
+            assertThat(responseNotSetText.getText()).isEqualTo("Hello World 1");
+        } catch (Throwable t) {
+            Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+                    Helper.LOCAL_TEST_FILES_DIR);
+            throw t;
+        }
     }
 
     @Test
     @FlakyTest(bugId = 192418800)
     public void testUiTranslation_customTextView() throws Throwable {
-        // Enable CTS ContentCaptureService
-        CtsContentCaptureService contentcaptureService = enableContentCaptureService();
-        // Start Activity and get needed information
-        final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
+        try {
+            // Enable CTS ContentCaptureService
+            CtsContentCaptureService contentcaptureService = enableContentCaptureService();
+            // Start Activity and get needed information
+            final List<AutofillId> views = startCustomTextViewActivityAndGetViewsForTranslation();
 
-        // Wait session created and get the ConttCaptureContext from ContentCaptureService
-        final ContentCaptureContext contentCaptureContext =
-                getContentCaptureContextFromContentCaptureService(contentcaptureService);
+            // Wait session created and get the ConttCaptureContext from ContentCaptureService
+            final ContentCaptureContext contentCaptureContext =
+                    getContentCaptureContextFromContentCaptureService(contentcaptureService);
 
-        // enable CTS TranslationService
-        mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
-        Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
+            // enable CTS TranslationService
+            mTranslationServiceServiceWatcher = CtsTranslationService.setServiceWatcher();
+            Helper.setTemporaryTranslationService(CtsTranslationService.SERVICE_NAME);
 
-        final String translatedText = "success";
-        // Set response
-        final TranslationResponse expectedResponse =
-                createViewsTranslationResponse(views, translatedText);
-        sTranslationReplier.addResponse(expectedResponse);
+            final String translatedText = "success";
+            // Set response
+            final TranslationResponse expectedResponse =
+                    createViewsTranslationResponse(views, translatedText);
+            sTranslationReplier.addResponse(expectedResponse);
 
-        startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+            startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
 
-        // Verify result.
-        assertThat(mCustomTextView.isMyTagTranslationSupported()).isTrue();
-        final UiObject2 customText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
-                CustomTextViewActivity.ID_CUSTOM_TEXT);
-        assertThat(customText).isNotNull();
-        assertThat(customText.getText()).isEqualTo(translatedText);
+            // Verify result.
+            assertThat(mCustomTextView.isMyTagTranslationSupported()).isTrue();
+            final UiObject2 customText = Helper.findObjectByResId(Helper.ACTIVITY_PACKAGE,
+                    CustomTextViewActivity.ID_CUSTOM_TEXT);
+            assertThat(customText).isNotNull();
+            assertThat(customText.getText()).isEqualTo(translatedText);
 
-        finishUiTranslation(contentCaptureContext);
+            finishUiTranslation(contentCaptureContext);
 
-        assertThat(customText.getText()).isEqualTo("Hello World 2");
+            assertThat(customText.getText()).isEqualTo("Hello World 2");
+        } catch (Throwable t) {
+            Helper.takeScreenshotAndSave(sContext, TestNameUtils.getCurrentTestName(),
+                    Helper.LOCAL_TEST_FILES_DIR);
+            throw t;
+        }
     }
 
     private void startUiTranslation(boolean shouldPadContent, List<AutofillId> views,
diff --git a/tests/uwb/Android.bp b/tests/uwb/Android.bp
index 7bcdc37..33b8441 100644
--- a/tests/uwb/Android.bp
+++ b/tests/uwb/Android.bp
@@ -18,11 +18,12 @@
 
 android_test {
     name: "CtsUwbTestCases",
-    defaults: ["cts_defaults"],
+    defaults: ["cts_defaults", "framework-uwb-cts-defaults"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "general-tests",
+        "mts-uwb",
     ],
     libs: ["android.test.runner"],
     static_libs: [
diff --git a/tests/uwb/AndroidTest.xml b/tests/uwb/AndroidTest.xml
index f0bb20a..3104ec5 100644
--- a/tests/uwb/AndroidTest.xml
+++ b/tests/uwb/AndroidTest.xml
@@ -19,6 +19,7 @@
     <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" />
+    <option name="config-descriptor:metadata" key="mainline-param" value="com.google.android.uwb.apex" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsUwbTestCases.apk" />
@@ -26,4 +27,7 @@
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.uwb.cts" />
     </test>
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.uwb" />
+    </object>
 </configuration>
diff --git a/tests/uwb/OWNERS b/tests/uwb/OWNERS
index 7ba57cf..c4ad416 100644
--- a/tests/uwb/OWNERS
+++ b/tests/uwb/OWNERS
@@ -1,5 +1,2 @@
-# Bug component: 898555
-bstack@google.com
-eliptus@google.com
-jsolnit@google.com
-zachoverflow@google.com
+# Bug component: 1042770
+include platform/packages/modules/Uwb:/OWNERS
diff --git a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
index d57a636..bc5c2fd 100644
--- a/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
+++ b/tests/uwb/src/android/uwb/cts/RangingMeasurementTest.java
@@ -17,6 +17,7 @@
 package android.uwb.cts;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.os.Parcel;
@@ -38,6 +39,8 @@
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RangingMeasurementTest {
+    private static final int TEST_RSSI_DBM = -80;
+    private static final int INVALID_RSSI_DBM = -129;
 
     @Test
     public void testBuilder() {
@@ -45,7 +48,12 @@
         UwbAddress address = UwbTestUtils.getUwbAddress(false);
         long time = SystemClock.elapsedRealtimeNanos();
         AngleOfArrivalMeasurement angleMeasurement = UwbTestUtils.getAngleOfArrivalMeasurement();
+        AngleOfArrivalMeasurement destinationAngleMeasurement =
+                UwbTestUtils.getAngleOfArrivalMeasurement();
         DistanceMeasurement distanceMeasurement = UwbTestUtils.getDistanceMeasurement();
+        int los = RangingMeasurement.NLOS;
+        int measurementFocus = RangingMeasurement.MEASUREMENT_FOCUS_RANGE;
+
 
         RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
 
@@ -58,17 +66,45 @@
         builder.setAngleOfArrivalMeasurement(angleMeasurement);
         tryBuild(builder, false);
 
+        builder.setDestinationAngleOfArrivalMeasurement(destinationAngleMeasurement);
+        tryBuild(builder, false);
+
         builder.setDistanceMeasurement(distanceMeasurement);
         tryBuild(builder, false);
 
+        builder.setRssiDbm(TEST_RSSI_DBM);
+        tryBuild(builder, false);
+
         builder.setRemoteDeviceAddress(address);
+        tryBuild(builder, true);
+
+        builder.setLineOfSight(los);
+        tryBuild(builder, true);
+
+        builder.setMeasurementFocus(measurementFocus);
         RangingMeasurement measurement = tryBuild(builder, true);
 
         assertEquals(status, measurement.getStatus());
         assertEquals(address, measurement.getRemoteDeviceAddress());
         assertEquals(time, measurement.getElapsedRealtimeNanos());
         assertEquals(angleMeasurement, measurement.getAngleOfArrivalMeasurement());
+        assertEquals(destinationAngleMeasurement,
+                measurement.getDestinationAngleOfArrivalMeasurement());
         assertEquals(distanceMeasurement, measurement.getDistanceMeasurement());
+        assertEquals(los, measurement.getLineOfSight());
+        assertEquals(measurementFocus, measurement.getMeasurementFocus());
+        assertEquals(TEST_RSSI_DBM, measurement.getRssiDbm());
+    }
+
+    @Test
+    public void testInvalidRssi() {
+        RangingMeasurement.Builder builder = new RangingMeasurement.Builder();
+        try {
+            builder.setRssiDbm(INVALID_RSSI_DBM);
+            fail("Expected RangingMeasurement.Builder.setRssiDbm() to fail");
+        } catch (Exception e) {
+            assertTrue(e.getMessage().contains("Invalid"));
+        }
     }
 
     private RangingMeasurement tryBuild(RangingMeasurement.Builder builder,
diff --git a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
index b9725f1..c638876 100644
--- a/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
+++ b/tests/uwb/src/android/uwb/cts/RangingSessionTest.java
@@ -16,6 +16,8 @@
 
 package android.uwb.cts;
 
+import static android.uwb.RangingSession.Callback.REASON_BAD_PARAMETERS;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -30,10 +32,11 @@
 
 import android.os.PersistableBundle;
 import android.os.RemoteException;
-import android.uwb.IUwbAdapter;
+import android.uwb.IUwbAdapter2;
 import android.uwb.RangingReport;
 import android.uwb.RangingSession;
 import android.uwb.SessionHandle;
+import android.uwb.UwbAddress;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -53,6 +56,7 @@
 public class RangingSessionTest {
     private static final Executor EXECUTOR = UwbTestUtils.getExecutor();
     private static final PersistableBundle PARAMS = new PersistableBundle();
+    private static final UwbAddress UWB_ADDRESS = UwbAddress.fromBytes(new byte[] {0x00, 0x56});
     private static final @RangingSession.Callback.Reason int REASON =
             RangingSession.Callback.REASON_GENERIC_ERROR;
 
@@ -60,7 +64,7 @@
     public void testOnRangingOpened_OnOpenSuccessCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
@@ -73,10 +77,33 @@
     }
 
     @Test
+    public void testOnRangingOpened_OnServiceDiscoveredConnectedCalled() {
+        SessionHandle handle = new SessionHandle(123);
+        RangingSession.Callback callback = mock(RangingSession.Callback.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.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());
+
+        session.onServiceDiscovered(PARAMS);
+        verify(callback, times(1)).onServiceDiscovered(eq(PARAMS));
+
+        session.onServiceConnected(PARAMS);
+        verify(callback, times(1)).onServiceConnected(eq(PARAMS));
+    }
+
+
+    @Test
     public void testOnRangingOpened_CannotOpenClosedSession() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
         session.onRangingOpened();
@@ -100,7 +127,7 @@
     public void testOnRangingClosed_OnClosedCalledWhenSessionNotOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
@@ -116,7 +143,7 @@
     public void testOnRangingClosed_OnClosedCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         session.onRangingStarted(PARAMS);
         session.onRangingClosed(REASON, PARAMS);
@@ -131,7 +158,7 @@
     public void testOnRangingResult_OnReportReceivedCalled() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         verifyOpenState(session, false);
 
@@ -147,7 +174,7 @@
     public void testStart_CannotStartIfAlreadyStarted() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
         session.onRangingOpened();
@@ -164,7 +191,7 @@
     public void testStop_CannotStopIfAlreadyStopped() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.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());
@@ -180,45 +207,137 @@
     }
 
     @Test
-    public void testReconfigure_OnlyWhenOpened() throws RemoteException {
+    public void testCallbacks_OnlyWhenOpened() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
+        doAnswer(new OpenAnswer(session)).when(adapter).openRanging(
+                any(), any(), any(), any(), any());
         doAnswer(new StartAnswer(session)).when(adapter).startRanging(any(), any());
         doAnswer(new ReconfigureAnswer(session)).when(adapter).reconfigureRanging(any(), any());
+        doAnswer(new SuspendAnswer(session)).when(adapter).suspend(any(), any());
+        doAnswer(new ResumeAnswer(session)).when(adapter).resume(any(), any());
+        doAnswer(new ControleeAddAnswer(session)).when(adapter).addControlee(any(), any());
+        doAnswer(new ControleeRemoveAnswer(session)).when(adapter).removeControlee(any(), any());
+        doAnswer(new DataSendAnswer(session)).when(adapter).sendData(any(), any(), any(), any());
+        doAnswer(new StopAnswer(session)).when(adapter).stopRanging(any());
 
         verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
         verify(callback, times(0)).onReconfigured(any());
         verifyOpenState(session, false);
 
+        session.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS);
+        verifyOpenState(session, false);
+        verify(callback, times(1)).onOpenFailed(
+                REASON_BAD_PARAMETERS, PARAMS);
+
         session.onRangingOpened();
+        verifyOpenState(session, true);
+        verify(callback, times(1)).onStarted(any());
         verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
         verify(callback, times(1)).onReconfigured(any());
+        verifyThrowIllegalState(() -> session.suspend(PARAMS));
+        verify(callback, times(0)).onSuspended(any());
+        verifyThrowIllegalState(() -> session.resume(PARAMS));
+        verify(callback, times(0)).onResumed(any());
+        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+        verify(callback, times(1)).onControleeAdded(any());
+        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+        verify(callback, times(1)).onControleeRemoved(any());
+        verifyThrowIllegalState(() -> session.sendData(
+                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+        verify(callback, times(0)).onDataSent(any(), any());
+
+        session.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
         verifyOpenState(session, true);
+        verify(callback, times(1)).onStartFailed(
+                REASON_BAD_PARAMETERS, PARAMS);
 
         session.onRangingStarted(PARAMS);
+        verifyOpenState(session, true);
         verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
         verify(callback, times(2)).onReconfigured(any());
-        verifyOpenState(session, true);
+        verifyNoThrowIllegalState(() -> session.reconfigure(null));
+        verify(callback, times(2)).onReconfigureFailed(
+                eq(REASON_BAD_PARAMETERS), any());
+        verifyNoThrowIllegalState(() -> session.suspend(PARAMS));
+        verify(callback, times(1)).onSuspended(any());
+        verifyNoThrowIllegalState(() -> session.suspend(null));
+        verify(callback, times(1)).onSuspendFailed(
+                eq(REASON_BAD_PARAMETERS), any());
+        verifyNoThrowIllegalState(() -> session.resume(PARAMS));
+        verify(callback, times(1)).onResumed(any());
+        verifyNoThrowIllegalState(() -> session.resume(null));
+        verify(callback, times(1)).onResumeFailed(
+                eq(REASON_BAD_PARAMETERS), any());
+        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+        verify(callback, times(2)).onControleeAdded(any());
+        verifyNoThrowIllegalState(() -> session.addControlee(null));
+        verify(callback, times(1)).onControleeAddFailed(
+                eq(REASON_BAD_PARAMETERS), any());
+        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+        verify(callback, times(2)).onControleeRemoved(any());
+        verifyNoThrowIllegalState(() -> session.removeControlee(null));
+        verify(callback, times(1)).onControleeRemoveFailed(
+                eq(REASON_BAD_PARAMETERS), any());
+        verifyNoThrowIllegalState(() -> session.sendData(
+                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+        verify(callback, times(1)).onDataSent(any(), any());
+        verifyNoThrowIllegalState(() -> session.sendData(
+                null, PARAMS, new byte[] {0x05, 0x1}));
+        verify(callback, times(1)).onDataSendFailed(
+                eq(null), eq(REASON_BAD_PARAMETERS), any());
 
-        session.onRangingStopped(REASON, PARAMS);
+        session.onDataReceived(UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
+        verify(callback, times(1)).onDataReceived(
+                UWB_ADDRESS, PARAMS, new byte[] {0x5, 0x7});
+        session.onDataReceiveFailed(UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
+        verify(callback, times(1)).onDataReceiveFailed(
+                UWB_ADDRESS, REASON_BAD_PARAMETERS, PARAMS);
+
+        session.stop();
+        verifyOpenState(session, true);
+        verify(callback, times(1)).onStopped(REASON, PARAMS);
+
         verifyNoThrowIllegalState(() -> session.reconfigure(PARAMS));
         verify(callback, times(3)).onReconfigured(any());
-        verifyOpenState(session, true);
+        verifyThrowIllegalState(() -> session.suspend(PARAMS));
+        verify(callback, times(1)).onSuspended(any());
+        verifyThrowIllegalState(() -> session.resume(PARAMS));
+        verify(callback, times(1)).onResumed(any());
+        verifyNoThrowIllegalState(() -> session.addControlee(PARAMS));
+        verify(callback, times(3)).onControleeAdded(any());
+        verifyNoThrowIllegalState(() -> session.removeControlee(PARAMS));
+        verify(callback, times(3)).onControleeRemoved(any());
+        verifyThrowIllegalState(() -> session.sendData(
+                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+        verify(callback, times(1)).onDataSent(any(), any());
 
+        session.close();
+        verifyOpenState(session, false);
+        verify(callback, times(1)).onClosed(REASON, PARAMS);
 
-        session.onRangingClosed(REASON, PARAMS);
         verifyThrowIllegalState(() -> session.reconfigure(PARAMS));
         verify(callback, times(3)).onReconfigured(any());
-        verifyOpenState(session, false);
+        verifyThrowIllegalState(() -> session.suspend(PARAMS));
+        verify(callback, times(1)).onSuspended(any());
+        verifyThrowIllegalState(() -> session.resume(PARAMS));
+        verify(callback, times(1)).onResumed(any());
+        verifyThrowIllegalState(() -> session.addControlee(PARAMS));
+        verify(callback, times(3)).onControleeAdded(any());
+        verifyThrowIllegalState(() -> session.removeControlee(PARAMS));
+        verify(callback, times(3)).onControleeRemoved(any());
+        verifyThrowIllegalState(() -> session.sendData(
+                UWB_ADDRESS, PARAMS, new byte[] {0x05, 0x1}));
+        verify(callback, times(1)).onDataSent(any(), any());
     }
 
     @Test
     public void testClose_NoCallbackUntilInvoked() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         session.onRangingOpened();
 
@@ -247,7 +366,7 @@
     public void testClose_OnClosedCalled() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
         session.onRangingOpened();
@@ -260,7 +379,7 @@
     public void testClose_CannotInteractFurther() throws RemoteException {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
         doAnswer(new CloseAnswer(session)).when(adapter).closeRanging(any());
         session.close();
@@ -275,7 +394,7 @@
     public void testOnRangingResult_OnReportReceivedCalledWhenOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
         assertFalse(session.isOpen());
@@ -292,7 +411,7 @@
     public void testOnRangingResult_OnReportReceivedNotCalledWhenNotOpen() {
         SessionHandle handle = new SessionHandle(123);
         RangingSession.Callback callback = mock(RangingSession.Callback.class);
-        IUwbAdapter adapter = mock(IUwbAdapter.class);
+        IUwbAdapter2 adapter = mock(IUwbAdapter2.class);
         RangingSession session = new RangingSession(EXECUTOR, callback, adapter, handle);
 
         assertFalse(session.isOpen());
@@ -332,6 +451,23 @@
         }
     }
 
+    class OpenAnswer extends AdapterAnswer {
+        OpenAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onRangingOpened();
+            } else {
+                mSession.onRangingOpenFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
     class StartAnswer extends AdapterAnswer {
         StartAnswer(RangingSession session) {
             super(session);
@@ -339,7 +475,12 @@
 
         @Override
         public Object answer(InvocationOnMock invocation) {
-            mSession.onRangingStarted(PARAMS);
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onRangingStarted(PARAMS);
+            } else {
+                mSession.onRangingStartFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
             return null;
         }
     }
@@ -351,7 +492,97 @@
 
         @Override
         public Object answer(InvocationOnMock invocation) {
-            mSession.onRangingReconfigured(PARAMS);
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onRangingReconfigured(PARAMS);
+            } else {
+                mSession.onRangingReconfigureFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
+    class SuspendAnswer extends AdapterAnswer {
+        SuspendAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onRangingSuspended(PARAMS);
+            } else {
+                mSession.onRangingSuspendFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
+    class ResumeAnswer extends AdapterAnswer {
+        ResumeAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onRangingResumed(PARAMS);
+            } else {
+                mSession.onRangingResumeFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
+    class ControleeAddAnswer extends AdapterAnswer {
+        ControleeAddAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onControleeAdded(PARAMS);
+            } else {
+                mSession.onControleeAddFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
+    class ControleeRemoveAnswer extends AdapterAnswer {
+        ControleeRemoveAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            PersistableBundle argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onControleeRemoved(PARAMS);
+            } else {
+                mSession.onControleeRemoveFailed(REASON_BAD_PARAMETERS, PARAMS);
+            }
+            return null;
+        }
+    }
+
+    class DataSendAnswer extends AdapterAnswer {
+        DataSendAnswer(RangingSession session) {
+            super(session);
+        }
+
+        @Override
+        public Object answer(InvocationOnMock invocation) {
+            UwbAddress argParams = invocation.getArgument(1);
+            if (argParams != null) {
+                mSession.onDataSent(UWB_ADDRESS, PARAMS);
+            } else {
+                mSession.onDataSendFailed(null, REASON_BAD_PARAMETERS, PARAMS);
+            }
             return null;
         }
     }
diff --git a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
index a887709..0d85c03 100644
--- a/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
+++ b/tests/uwb/src/android/uwb/cts/UwbManagerTest.java
@@ -18,11 +18,14 @@
 
 import static android.Manifest.permission.UWB_PRIVILEGED;
 import static android.Manifest.permission.UWB_RANGING;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_DISABLED;
+import static android.uwb.UwbManager.AdapterStateCallback.STATE_ENABLED_INACTIVE;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
@@ -46,10 +49,13 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executors;
@@ -66,12 +72,43 @@
 
     private final Context mContext = InstrumentationRegistry.getContext();
     private UwbManager mUwbManager;
+    private String mDefaultChipId;
 
     @Before
-    public void setup() {
+    public void setup() throws Exception {
         mUwbManager = mContext.getSystemService(UwbManager.class);
         assumeTrue(UwbTestUtils.isUwbSupported(mContext));
         assertThat(mUwbManager).isNotNull();
+
+        // Ensure UWB is toggled on.
+        ShellIdentityUtils.invokeWithShellPermissions(() -> {
+            if (!mUwbManager.isUwbEnabled()) {
+                try {
+                    setUwbEnabledAndWaitForCompletion(true);
+                } catch (Exception e) {
+                    fail("Exception while processing UWB toggle " + e);
+                }
+            }
+            mDefaultChipId = mUwbManager.getDefaultChipId();
+        });
+    }
+
+    // Should be invoked with shell permissions.
+    private void setUwbEnabledAndWaitForCompletion(boolean enabled) throws Exception {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        int adapterState = enabled ? STATE_ENABLED_INACTIVE : STATE_DISABLED;
+        AdapterStateCallback adapterStateCallback =
+                new AdapterStateCallback(countDownLatch, adapterState);
+        try {
+            mUwbManager.registerAdapterStateCallback(
+                    Executors.newSingleThreadExecutor(), adapterStateCallback);
+            mUwbManager.setUwbEnabled(enabled);
+            assertThat(countDownLatch.await(2, TimeUnit.SECONDS)).isTrue();
+            assertThat(mUwbManager.isUwbEnabled()).isEqualTo(enabled);
+            assertThat(adapterStateCallback.state).isEqualTo(adapterState);
+        } finally {
+            mUwbManager.unregisterAdapterStateCallback(adapterStateCallback);
+        }
     }
 
     @Test
@@ -89,6 +126,48 @@
     }
 
     @Test
+    public void testGetSpecificationInfoWithChipId() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            PersistableBundle persistableBundle =
+                    mUwbManager.getSpecificationInfo(mDefaultChipId);
+            assertThat(persistableBundle).isNotNull();
+            assertThat(persistableBundle.isEmpty()).isFalse();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testGetChipIds() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            List<String> chipIds = mUwbManager.getChipIds();
+            assertThat(chipIds).isNotEmpty();
+            assertThat(chipIds).contains(mDefaultChipId);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testGetSpecificationInfoWithInvalidChipId() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            assertThrows(IllegalArgumentException.class,
+                    () -> mUwbManager.getSpecificationInfo("invalidChipId"));
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testGetSpecificationInfoWithoutUwbPrivileged() {
         try {
             mUwbManager.getSpecificationInfo();
@@ -100,6 +179,18 @@
         }
     }
 
+    @Test
+    public void testGetSpecificationInfoWithChipIdWithoutUwbPrivileged() {
+        try {
+            mUwbManager.getSpecificationInfo(mDefaultChipId);
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
 
     @Test
     public void testElapsedRealtimeResolutionNanos() {
@@ -114,6 +205,32 @@
     }
 
     @Test
+    public void testElapsedRealtimeResolutionNanosWithChipId() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            assertThat(mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId) >= 0L)
+                    .isTrue();
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testElapsedRealtimeResolutionNanosWithInvalidChipId() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            assertThrows(IllegalArgumentException.class,
+                    () -> mUwbManager.elapsedRealtimeResolutionNanos("invalidChipId"));
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
     public void testElapsedRealtimeResolutionNanosWithoutUwbPrivileged() {
         try {
             mUwbManager.elapsedRealtimeResolutionNanos();
@@ -125,6 +242,144 @@
         }
     }
 
+    @Test
+    public void testElapsedRealtimeResolutionNanosWithChipIdWithoutUwbPrivileged() {
+        try {
+            mUwbManager.elapsedRealtimeResolutionNanos(mDefaultChipId);
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testAddServiceProfileWithoutUwbPrivileged() {
+        try {
+            mUwbManager.addServiceProfile(new PersistableBundle());
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testRemoveServiceProfileWithoutUwbPrivileged() {
+        try {
+            mUwbManager.removeServiceProfile(new PersistableBundle());
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+
+    @Test
+    public void testGetAllServiceProfilesWithoutUwbPrivileged() {
+        try {
+            mUwbManager.getAllServiceProfiles();
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testGetAdfProvisioningAuthoritiesWithoutUwbPrivileged() {
+        try {
+            mUwbManager.getAdfProvisioningAuthorities(new PersistableBundle());
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testGetAdfCertificateInfoWithoutUwbPrivileged() {
+        try {
+            mUwbManager.getAdfCertificateInfo(new PersistableBundle());
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testChipIdsWithoutUwbPrivileged() {
+        try {
+            mUwbManager.getChipIds();
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    private class AdfProvisionStateCallback extends UwbManager.AdfProvisionStateCallback {
+        private final CountDownLatch mCountDownLatch;
+
+        public boolean onSuccessCalled;
+        public boolean onFailedCalled;
+
+        AdfProvisionStateCallback(@NonNull CountDownLatch countDownLatch) {
+            mCountDownLatch = countDownLatch;
+        }
+
+        @Override
+        public void onProfileAdfsProvisioned(@NonNull PersistableBundle params) {
+            onSuccessCalled = true;
+            mCountDownLatch.countDown();
+        }
+
+        @Override
+        public void onProfileAdfsProvisionFailed(int reason, @NonNull PersistableBundle params) {
+            onFailedCalled = true;
+            mCountDownLatch.countDown();
+        }
+    }
+
+    @Test
+    public void testProvisionProfileAdfByScriptWithoutUwbPrivileged() {
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        AdfProvisionStateCallback adfProvisionStateCallback =
+                new AdfProvisionStateCallback(countDownLatch);
+        try {
+            mUwbManager.provisionProfileAdfByScript(
+                    new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    adfProvisionStateCallback);
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
+    @Test
+    public void testRemoveProfileAdfWithoutUwbPrivileged() {
+        try {
+            mUwbManager.removeProfileAdf(new PersistableBundle());
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        }
+    }
+
     private class RangingSessionCallback implements RangingSession.Callback {
         private final CountDownLatch mCountDownLatch;
 
@@ -165,7 +420,26 @@
     }
 
     @Test
-    public void testOpenRangingSessionWithBadParams() throws Exception {
+    public void testOpenRangingSessionWithInvalidChipId() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch);
+        try {
+            // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            // Try to start a ranging session with invalid params, should fail.
+            assertThrows(IllegalArgumentException.class, () -> mUwbManager.openRangingSession(
+                    new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    rangingSessionCallback,
+                    "invalidChipId"));
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testOpenRangingSessionWithChipIdWithBadParams() throws Exception {
         UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
         CancellationSignal cancellationSignal = null;
         CountDownLatch countDownLatch = new CountDownLatch(1);
@@ -177,7 +451,35 @@
             cancellationSignal = mUwbManager.openRangingSession(
                     new PersistableBundle(),
                     Executors.newSingleThreadExecutor(),
-                    rangingSessionCallback);
+                    rangingSessionCallback,
+                    mDefaultChipId);
+            // Wait for the on start failed callback.
+            assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
+            assertThat(rangingSessionCallback.onOpenedCalled).isFalse();
+            assertThat(rangingSessionCallback.onOpenFailedCalled).isTrue();
+        } finally {
+            if (cancellationSignal != null) {
+                cancellationSignal.cancel();
+            }
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    @Test
+    public void testOpenRangingSessionWithInvalidChipIdWithBadParams() throws Exception {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        CancellationSignal cancellationSignal = null;
+        CountDownLatch countDownLatch = new CountDownLatch(1);
+        RangingSessionCallback rangingSessionCallback = new RangingSessionCallback(countDownLatch);
+        try {
+            // Needs UWB_PRIVILEGED & UWB_RANGING permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            // Try to start a ranging session with invalid params, should fail.
+            cancellationSignal = mUwbManager.openRangingSession(
+                    new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    rangingSessionCallback,
+                    mDefaultChipId);
             // Wait for the on start failed callback.
             assertThat(countDownLatch.await(1, TimeUnit.SECONDS)).isTrue();
             assertThat(rangingSessionCallback.onOpenedCalled).isFalse();
@@ -212,6 +514,26 @@
         }
     }
 
+    @Test
+    public void testOpenRangingSessionWithChipIdWithoutUwbPrivileged() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Only hold UWB_RANGING permission
+            uiAutomation.adoptShellPermissionIdentity(UWB_RANGING);
+            mUwbManager.openRangingSession(new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    new RangingSessionCallback(new CountDownLatch(1)),
+                    mDefaultChipId);
+            // should fail if the call was successful without UWB_PRIVILEGED permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     /**
      * Simulates the app holding UWB_PRIVILEGED permission, but not UWB_RANGING.
      */
@@ -234,6 +556,26 @@
         }
     }
 
+    @Test
+    public void testOpenRangingSessionWithChipIdWithoutUwbRanging() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity(UWB_PRIVILEGED);
+            mUwbManager.openRangingSession(new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    new RangingSessionCallback(new CountDownLatch(1)),
+                    mDefaultChipId);
+            // should fail if the call was successful without UWB_RANGING permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
     private AttributionSource getShellAttributionSourceWithRenouncedPermissions(
             @Nullable Set<String> renouncedPermissions) {
         try {
@@ -288,4 +630,70 @@
             uiAutomation.dropShellPermissionIdentity();
         }
     }
+
+    @Test
+    public void testOpenRangingSessionWithChipIdWithoutUwbRangingInNextAttributeSource() {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Only hold UWB_PRIVILEGED permission
+            uiAutomation.adoptShellPermissionIdentity();
+            Context shellContextWithUwbRangingRenounced =
+                    createShellContextWithRenouncedPermissionsAndAttributionSource(
+                            Set.of(UWB_RANGING));
+            UwbManager uwbManagerWithUwbRangingRenounced =
+                    shellContextWithUwbRangingRenounced.getSystemService(UwbManager.class);
+            uwbManagerWithUwbRangingRenounced.openRangingSession(new PersistableBundle(),
+                    Executors.newSingleThreadExecutor(),
+                    new RangingSessionCallback(new CountDownLatch(1)),
+                    mDefaultChipId);
+            // should fail if the call was successful without UWB_RANGING permission.
+            fail();
+        } catch (SecurityException e) {
+            /* pass */
+            Log.i(TAG, "Failed with expected security exception: " + e);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private class AdapterStateCallback implements UwbManager.AdapterStateCallback {
+        private final CountDownLatch mCountDownLatch;
+        private final @State Integer mWaitForState;
+        public int state;
+        public int reason;
+
+        AdapterStateCallback(@NonNull CountDownLatch countDownLatch,
+                @Nullable @State Integer waitForState) {
+            mCountDownLatch = countDownLatch;
+            mWaitForState = waitForState;
+        }
+
+        public void onStateChanged(@State int state, @StateChangedReason int reason) {
+            this.state = state;
+            this.reason = reason;
+            if (mWaitForState != null) {
+                if (mWaitForState == state) {
+                    mCountDownLatch.countDown();
+                }
+            } else {
+                mCountDownLatch.countDown();
+            }
+        }
+    }
+
+    @Test
+    public void testUwbStateToggle() throws Exception {
+        UiAutomation uiAutomation = getInstrumentation().getUiAutomation();
+        try {
+            // Needs UWB_PRIVILEGED permission which is held by shell.
+            uiAutomation.adoptShellPermissionIdentity();
+            assertThat(mUwbManager.isUwbEnabled()).isTrue();
+            assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_ENABLED_INACTIVE);
+            // Toggle the state
+            setUwbEnabledAndWaitForCompletion(false);
+            assertThat(mUwbManager.getAdapterState()).isEqualTo(STATE_DISABLED);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
index 3790b52..041b66c 100644
--- a/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
+++ b/tests/uwb/src/android/uwb/cts/UwbTestUtils.java
@@ -67,9 +67,13 @@
         return new RangingMeasurement.Builder()
                 .setDistanceMeasurement(getDistanceMeasurement())
                 .setAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
+                .setDestinationAngleOfArrivalMeasurement(getAngleOfArrivalMeasurement())
                 .setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos())
                 .setRemoteDeviceAddress(address != null ? address : getUwbAddress(false))
                 .setStatus(RangingMeasurement.RANGING_STATUS_SUCCESS)
+                .setLineOfSight(RangingMeasurement.NLOS)
+                .setMeasurementFocus(RangingMeasurement.MEASUREMENT_FOCUS_RANGE)
+                .setRssiDbm(-85)
                 .build();
     }
 
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 4a12d70..791843e 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
@@ -637,6 +637,8 @@
         charsKeyNames.add(CameraCharacteristics.CONTROL_ZOOM_RATIO_RANGE.getName());
         charsKeyNames.add(CameraCharacteristics.EDGE_AVAILABLE_EDGE_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.FLASH_INFO_AVAILABLE.getName());
+        charsKeyNames.add(CameraCharacteristics.FLASH_INFO_STRENGTH_MAXIMUM_LEVEL.getName());
+        charsKeyNames.add(CameraCharacteristics.FLASH_INFO_STRENGTH_DEFAULT_LEVEL.getName());
         charsKeyNames.add(CameraCharacteristics.HOT_PIXEL_AVAILABLE_HOT_PIXEL_MODES.getName());
         charsKeyNames.add(CameraCharacteristics.JPEG_AVAILABLE_THUMBNAIL_SIZES.getName());
         charsKeyNames.add(CameraCharacteristics.LENS_FACING.getName());
@@ -658,6 +660,8 @@
         charsKeyNames.add(CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH.getName());
         charsKeyNames.add(CameraCharacteristics.REQUEST_PARTIAL_RESULT_COUNT.getName());
         charsKeyNames.add(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES.getName());
+        charsKeyNames.add(CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES.getName());
+        charsKeyNames.add(CameraCharacteristics.REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_CROPPING_TYPE.getName());
@@ -668,6 +672,7 @@
         charsKeyNames.add(CameraCharacteristics.SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION.getName());
         charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_MAXIMUM_RESOLUTION_STREAM_COMBINATIONS.getName());
+        charsKeyNames.add(CameraCharacteristics.SCALER_MANDATORY_TEN_BIT_OUTPUT_STREAM_COMBINATIONS.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/res/config/cts-known-failures.xml b/tools/cts-tradefed/res/config/cts-known-failures.xml
index df18066..72ff5ce 100644
--- a/tools/cts-tradefed/res/config/cts-known-failures.xml
+++ b/tools/cts-tradefed/res/config/cts-known-failures.xml
@@ -255,7 +255,4 @@
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedManagedProfileOwnerTest#testSetCameraDisabledLogged" />
     <option name="compatibility:exclude-filter" value="CtsDevicePolicyManagerTestCases com.android.cts.devicepolicy.MixedProfileOwnerTest#testSetCameraDisabledLogged" />
 
-    <!-- b/204721335 -->
-    <option name="compatibility:exclude-filter" value="CtsWindowManagerJetpackTestCases android.server.wm.jetpack.SidecarTest#testSidecarInterface_onWindowLayoutChangeListener" />
-
 </configuration>
diff --git a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
index 9ddd8b1..f4da75b 100644
--- a/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
+++ b/tools/cts-tradefed/res/config/cts-on-gsi-exclude.xml
@@ -77,8 +77,8 @@
     <option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-GLES3.functional.prerequisite#*" />
     <option name="compatibility:module-arg" value="CtsDeqpTestCases:include-filter:dEQP-VK.api.smoke#*" />
 
-    <!-- b/183659262 Remove CtsPreferenceTestCases from cts-on-gsi -->
-    <option name="compatibility:exclude-filter" value="CtsPreferenceTestCases" />
+    <!-- b/183636777 Remove CtsShortcutManagerPackage4 from cts-on-gsi -->
+    <option name="compatibility:exclude-filter" value="CtsShortcutManagerPackage4" />
 
     <!-- b/185451791. Can't have single overlay package for both AOSP version and Google-signed mainline modules -->
     <option name="compatibility:exclude-filter" value="CtsWifiTestCases android.net.wifi.cts.ConcurrencyTest#testPersistentGroupOperation" />
diff --git a/tools/cts-tradefed/res/config/cts-sim-include.xml b/tools/cts-tradefed/res/config/cts-sim-include.xml
index 656c008..522c8761 100644
--- a/tools/cts-tradefed/res/config/cts-sim-include.xml
+++ b/tools/cts-tradefed/res/config/cts-sim-include.xml
@@ -40,7 +40,6 @@
     <option name="compatibility:include-filter" value="CtsTelephonyTestCases" />
     <option name="compatibility:include-filter" value="CtsTelephony2TestCases" />
     <option name="compatibility:include-filter" value="CtsTelephony3TestCases" />
-    <option name="compatibility:include-filter" value="CtsTelephonySdk28TestCases" />
     <option name="compatibility:include-filter" value="CtsTetheringTest" />
     <option name="compatibility:include-filter" value="CtsUsageStatsTestCases" />
     <option name="compatibility:include-filter" value="CtsVcnTestCases" />